Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
ebfdc0ffe3 | |||
020a5bc5b8 | |||
cab80c49c0 | |||
59c880b909 | |||
51b95e272c | |||
424a9ce7d1 | |||
ada0a7fa5a | |||
1766ae1e0f | |||
d34d0161b6 | |||
10f07029f6 | |||
cc78d3fc5f | |||
95aa73c614 | |||
5fe51d063c | |||
4ca3f4d8f2 | |||
636ac82bcc | |||
b22e627089 | |||
f6387323c2 | |||
ae8946378f | |||
f5c2fc5f14 | |||
048cd549dd |
310 changed files with 13415 additions and 3202 deletions
4
.config/docker_example.env
Normal file
4
.config/docker_example.env
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# db settings
|
||||||
|
POSTGRES_PASSWORD=example-foundkey-pass
|
||||||
|
POSTGRES_USER=example-foundkey-user
|
||||||
|
POSTGRES_DB=foundkey
|
|
@ -68,6 +68,17 @@ redis:
|
||||||
#prefix: example-prefix
|
#prefix: example-prefix
|
||||||
#db: 1
|
#db: 1
|
||||||
|
|
||||||
|
# ┌─────────────────────────────┐
|
||||||
|
#───┘ Elasticsearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
# Elasticsearch is optional.
|
||||||
|
#elasticsearch:
|
||||||
|
# host: localhost
|
||||||
|
# port: 9200
|
||||||
|
# ssl: false
|
||||||
|
# user:
|
||||||
|
# pass:
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
|
||||||
|
@ -142,9 +153,3 @@ redis:
|
||||||
# info: /twemoji/1f440.svg
|
# info: /twemoji/1f440.svg
|
||||||
# notFound: /twemoji/2049.svg
|
# notFound: /twemoji/2049.svg
|
||||||
# error: /twemoji/1f480.svg
|
# error: /twemoji/1f480.svg
|
||||||
|
|
||||||
# Whether it should be allowed to fetch content in ActivityPub form without HTTP signatures.
|
|
||||||
# It is recommended to leave this as default to improve the effectiveness of instance blocks.s
|
|
||||||
# However, note that while this prevents fetching in ActivityPub form, it could still be scraped
|
|
||||||
# from the API or other representations if the other side is determined to do so.
|
|
||||||
#allowUnsignedFetches: false
|
|
||||||
|
|
12
.dockerignore
Normal file
12
.dockerignore
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.autogen
|
||||||
|
.config
|
||||||
|
.woodpecker
|
||||||
|
Dockerfile
|
||||||
|
build/
|
||||||
|
built/
|
||||||
|
db/
|
||||||
|
docker-compose.yml
|
||||||
|
elasticsearch/
|
||||||
|
node_modules/
|
||||||
|
redis/
|
||||||
|
files/
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -18,9 +18,14 @@
|
||||||
node_modules
|
node_modules
|
||||||
report.*.json
|
report.*.json
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
|
||||||
# config
|
# config
|
||||||
/.config/*
|
/.config/*
|
||||||
!/.config/example.yml
|
!/.config/example.yml
|
||||||
|
!/.config/docker_example.env
|
||||||
|
|
||||||
# misskey
|
# misskey
|
||||||
/build
|
/build
|
||||||
|
@ -28,6 +33,7 @@ built
|
||||||
/data
|
/data
|
||||||
/.cache-loader
|
/.cache-loader
|
||||||
/db
|
/db
|
||||||
|
/elasticsearch
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
*.pem
|
*.pem
|
||||||
run.bat
|
run.bat
|
||||||
|
|
2
.mailmap
2
.mailmap
|
@ -6,7 +6,7 @@ Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast+git@toast.cafe>
|
||||||
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast@toast.cafe>
|
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast@toast.cafe>
|
||||||
Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com>
|
Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com>
|
||||||
Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> <ehsan.javadynia@gmail.com>
|
Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> <ehsan.javadynia@gmail.com>
|
||||||
Norm <normandy@biribiri.dev>
|
Francis Dinh <normandy@biribiri.dev>
|
||||||
Hakaba Hitoyo <tsukadayoshio@gmail.com> Hakaba Hitoyo <example@example.com>
|
Hakaba Hitoyo <tsukadayoshio@gmail.com> Hakaba Hitoyo <example@example.com>
|
||||||
Johann150 <johann.galle@protonmail.com> <johann@qwertqwefsday.eu>
|
Johann150 <johann.galle@protonmail.com> <johann@qwertqwefsday.eu>
|
||||||
Michcio <public+git@meekchopp.es> <michcio@noreply.akkoma>
|
Michcio <public+git@meekchopp.es> <michcio@noreply.akkoma>
|
||||||
|
|
13
.woodpecker/misskey/test.yml
Normal file
13
.woodpecker/misskey/test.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
url: 'http://misskey.local'
|
||||||
|
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
db:
|
||||||
|
host: postgres
|
||||||
|
port: 5432
|
||||||
|
db: test-misskey
|
||||||
|
user: postgres
|
||||||
|
pass: ''
|
||||||
|
redis:
|
||||||
|
host: redis
|
||||||
|
port: 6379
|
|
@ -17,4 +17,32 @@ pipeline:
|
||||||
commands:
|
commands:
|
||||||
- yarn install
|
- yarn install
|
||||||
- git diff --exit-code yarn.lock
|
- git diff --exit-code yarn.lock
|
||||||
|
- cp .woodpecker/misskey/test.yml .config
|
||||||
- yarn build
|
- yarn build
|
||||||
|
mocha:
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
image: node:18.6.0
|
||||||
|
commands:
|
||||||
|
- yarn mocha
|
||||||
|
e2e:
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
image: cypress/included:10.3.0
|
||||||
|
commands:
|
||||||
|
- npm run start:test &
|
||||||
|
- sleep 30 # wait for server to start
|
||||||
|
- cypress run --browser chrome
|
||||||
|
# TODO: upload screenshots and video artifacts?
|
||||||
|
# would need some kind of storage though
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=test-misskey
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
redis:
|
||||||
|
image: redis:6
|
||||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -11,33 +11,6 @@ Unreleased changes should not be listed in this file.
|
||||||
Instead, run `git shortlog --format='%h %s' --group=trailer:changelog <last tag>..` to see unreleased changes; replace `<last tag>` with the tag you wish to compare from.
|
Instead, run `git shortlog --format='%h %s' --group=trailer:changelog <last tag>..` to see unreleased changes; replace `<last tag>` with the tag you wish to compare from.
|
||||||
If you are a contributor, please read [CONTRIBUTING.md, section "Changelog Trailer"](./CONTRIBUTING.md#changelog-trailer) on what to do instead.
|
If you are a contributor, please read [CONTRIBUTING.md, section "Changelog Trailer"](./CONTRIBUTING.md#changelog-trailer) on what to do instead.
|
||||||
|
|
||||||
## 13.0.0-preview6 - 2023-07-02
|
|
||||||
|
|
||||||
## Added
|
|
||||||
- **BREAKING** activitypub: validate fetch signatures
|
|
||||||
Fetching the ActivityPub representation of something now requires a valid HTTP signature.
|
|
||||||
- client: add MFM functions `position`, `scale`, `fg`, `bg`
|
|
||||||
- server: add webhook stat to nodeinfo
|
|
||||||
- activitypub: handle incoming Update Note activities
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
- client: change followers only icon to closed lock
|
|
||||||
- client: disable sound for received note by default
|
|
||||||
- client: always forbid MFM overflow
|
|
||||||
- make mutes case insensitive
|
|
||||||
- activitypub: improve JSON-LD context
|
|
||||||
The context now properly notes the `@type`s of defined attributes.
|
|
||||||
- docker: only publish port on localhost
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
- server: fix internal download in emoji import
|
|
||||||
- server: replace unzipper with decompress
|
|
||||||
|
|
||||||
## Removed
|
|
||||||
- migrate note favorites to clips
|
|
||||||
If you previously had favorites they will now be in a clip called "⭐".
|
|
||||||
If you want to add a note as a "favorite" you can use the menu item "Clip".
|
|
||||||
|
|
||||||
## 13.0.0-preview5 - 2023-05-23
|
## 13.0.0-preview5 - 2023-05-23
|
||||||
This release contains 6 breaking changes and 1 security update.
|
This release contains 6 breaking changes and 1 security update.
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,6 @@ representative at an online or offline event.
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement via email at
|
reported to the community leaders responsible for enforcement via email at
|
||||||
johann<EFBFBD>qwertqwefsday.eu and/or toast<73>bunkerlabs.net .
|
johann<EFBFBD>qwertqwefsday.eu and/or toast<73>bunkerlabs.net .
|
||||||
(The at sign has been replaced so that spammers do not find these email addresses easily.
|
|
||||||
If you are a human you hopefully know what to do.)
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|
|
@ -11,9 +11,13 @@ Please understand that in such cases we might edit your issue to translate it, t
|
||||||
## Development platform
|
## Development platform
|
||||||
FoundKey generally assumes that it is running on a Unix-like platform (e.g. Linux or macOS). If you are using Windows for development, we highly suggest using the Windows Subsystem for Linux (WSL) as the development environment.
|
FoundKey generally assumes that it is running on a Unix-like platform (e.g. Linux or macOS). If you are using Windows for development, we highly suggest using the Windows Subsystem for Linux (WSL) as the development environment.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
See [ROADMAP.md](./ROADMAP.md)
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
Issues are intended for feature requests and bug tracking.
|
Issues are intended for feature requests and bug tracking.
|
||||||
Please note that in general, we are not looking for completely new features to add, but quality of life improvements will be considered.
|
|
||||||
|
For technical support or if you are not sure if what you are experiencing is a bug you can talk to people on the [IRC server](https://irc.akkoma.dev) in the `#foundkey` channel first.
|
||||||
|
|
||||||
Please do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
Please do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
||||||
|
|
||||||
|
@ -21,6 +25,7 @@ Please do not close issues that are about to be resolved. It should remain open
|
||||||
branch|what it's for
|
branch|what it's for
|
||||||
---|---
|
---|---
|
||||||
main|development branch
|
main|development branch
|
||||||
|
translate|managed by weblate, see [section about translation](#Translation)
|
||||||
|
|
||||||
For a production environment you might not want to follow the `main` branch directly but instead check out one of the git tags.
|
For a production environment you might not want to follow the `main` branch directly but instead check out one of the git tags.
|
||||||
|
|
||||||
|
@ -149,6 +154,8 @@ Here is the step by step checklist:
|
||||||
|
|
||||||
<small>a.k.a. Localization (l10n) or Internationalization (i18n)</small>
|
<small>a.k.a. Localization (l10n) or Internationalization (i18n)</small>
|
||||||
|
|
||||||
|
To translate text used in Foundkey, we use weblate at <https://translate.akkoma.dev/projects/foundkey/>.
|
||||||
|
|
||||||
Localization files are found in `/locales/` and are YAML files using the `yml` file extension.
|
Localization files are found in `/locales/` and are YAML files using the `yml` file extension.
|
||||||
The file name consists of the [IETF BCP 47](https://www.rfc-editor.org/info/bcp47) language code.
|
The file name consists of the [IETF BCP 47](https://www.rfc-editor.org/info/bcp47) language code.
|
||||||
|
|
||||||
|
@ -157,6 +164,39 @@ During development, it is useful to use the `npm run dev` command.
|
||||||
This command monitors the server-side and client-side source files and automatically builds them if they are modified.
|
This command monitors the server-side and client-side source files and automatically builds them if they are modified.
|
||||||
In addition, it will also automatically start the Misskey server process.
|
In addition, it will also automatically start the Misskey server process.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Test codes are located in [`/test`](/test).
|
||||||
|
|
||||||
|
### Run test
|
||||||
|
Create a config file.
|
||||||
|
```
|
||||||
|
cp test/test.yml .config/
|
||||||
|
```
|
||||||
|
Prepare DB/Redis for testing.
|
||||||
|
```
|
||||||
|
docker-compose -f test/docker-compose.yml up
|
||||||
|
```
|
||||||
|
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
||||||
|
|
||||||
|
Run all test.
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Run specify test
|
||||||
|
```
|
||||||
|
npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT="./test/tsconfig.json" npx mocha test/foo.ts --require ts-node/register
|
||||||
|
```
|
||||||
|
|
||||||
|
### e2e tests
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Continuous integration (CI)
|
||||||
|
|
||||||
|
Foundkey uses Woodpecker for executing automated tests and lints.
|
||||||
|
CI runs can be found at [ci.akkoma.dev](https://ci.akkoma.dev/FoundKeyGang/FoundKey)
|
||||||
|
Configuration files are located in `/.woodpecker/`.
|
||||||
|
|
||||||
## Vue
|
## Vue
|
||||||
Misskey uses Vue(v3) as its front-end framework.
|
Misskey uses Vue(v3) as its front-end framework.
|
||||||
- Use TypeScript functionality.
|
- Use TypeScript functionality.
|
||||||
|
|
36
Dockerfile
Normal file
36
Dockerfile
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
FROM node:18.12.1-alpine3.16 AS base
|
||||||
|
|
||||||
|
ARG NODE_ENV=production
|
||||||
|
|
||||||
|
WORKDIR /foundkey
|
||||||
|
|
||||||
|
ENV BUILD_DEPS autoconf automake file g++ gcc libc-dev libtool make nasm pkgconfig python3 zlib-dev git
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
RUN apk add --no-cache $BUILD_DEPS && \
|
||||||
|
git submodule update --init && \
|
||||||
|
yarn install && \
|
||||||
|
yarn build && \
|
||||||
|
rm -rf .git
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
ffmpeg \
|
||||||
|
tini
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
|
||||||
|
COPY --from=builder /foundkey/node_modules ./node_modules
|
||||||
|
COPY --from=builder /foundkey/built ./built
|
||||||
|
COPY --from=builder /foundkey/packages/backend/node_modules ./packages/backend/node_modules
|
||||||
|
COPY --from=builder /foundkey/packages/backend/built ./packages/backend/built
|
||||||
|
COPY --from=builder /foundkey/packages/foundkey-js/built ./packages/foundkey-js/built
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD ["npm", "run", "migrateandstart"]
|
||||||
|
|
|
@ -7,10 +7,6 @@ Look further up in the section to find the "base path" it is relative to.
|
||||||
|
|
||||||
All the backend code is in `/packages/backend/src`.
|
All the backend code is in `/packages/backend/src`.
|
||||||
|
|
||||||
The backend is started via `index.ts` which in turn starts `boot/index.ts`.
|
|
||||||
In the "boot" code is where the process is forked from the main process into additional and separate worker and frontend processes.
|
|
||||||
If you look into your operating system's process overview or similar, you might be able to see that the processes rename themselves accordingly.
|
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
For connecting to the database an ORM (object–relational mapping) is used.
|
For connecting to the database an ORM (object–relational mapping) is used.
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
<div align="center"><img src="./logo.svg" height="200" alt="Foundkey logo, an owl holding a key"/></div>
|
<div align="center"><img src="./logo.svg" height="200" alt="Foundkey logo, an owl holding a key"/></div>
|
||||||
|
|
||||||
# FoundKey
|
# FoundKey
|
||||||
FoundKey is a free and open source microblogging server compatible with ActivityPub.
|
FoundKey is a free and open source microblogging server compatible with ActivityPub. Forked from Misskey, FoundKey improves on maintainability and behaviour, while also bringing in useful features.
|
||||||
It is currently under **LIMITED MAINTENANCE** and is not well suited for large instances.
|
|
||||||
No more than 20 users per instance are recommended.
|
|
||||||
|
|
||||||
Forked from Misskey, FoundKey improves on maintainability and behaviour, while also bringing in useful features.
|
|
||||||
|
|
||||||
See the [changelog](./CHANGELOG.md) and [roadmap](./ROADMAP.md) for more on what's changed and future plans.
|
See the [changelog](./CHANGELOG.md) and [roadmap](./ROADMAP.md) for more on what's changed and future plans.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
FoundKey's documentation is a work in progress, which can be found in the `docs/` folder.
|
FoundKey's documentation is a work in progress, which can be found in the `docs/` folder.
|
||||||
Feel free to contribute some documentation.
|
|
||||||
|
In the meantime, much of the documentation on the [Misskey Hub](https://misskey-hub.net/) will also apply to FoundKey.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
If you're interested in helping out with the project, please read the [contributing guide](./CONTRIBUTING.md).
|
If you're interested in helping out with the project, please read the [contributing guide](./CONTRIBUTING.md).
|
||||||
|
|
12
cypress.config.ts
Normal file
12
cypress.config.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
// We've imported your old cypress plugins here.
|
||||||
|
// You may want to clean this up later by importing these.
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
return require('./cypress/plugins/index.js')(on, config)
|
||||||
|
},
|
||||||
|
baseUrl: 'http://localhost:61812',
|
||||||
|
},
|
||||||
|
})
|
149
cypress/e2e/basic.cy.js
Normal file
149
cypress/e2e/basic.cy.js
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
describe('Before setup instance', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetState();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||||
|
// waitを入れることでそれを防止できる
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setup instance', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/api/admin/accounts/create').as('signup');
|
||||||
|
|
||||||
|
cy.get('[data-cy-admin-username] input').type('admin');
|
||||||
|
cy.get('[data-cy-admin-password] input').type('admin1234');
|
||||||
|
cy.get('[data-cy-admin-ok]').click();
|
||||||
|
|
||||||
|
// なぜか動かない
|
||||||
|
//cy.wait('@signup').should('have.property', 'response.statusCode');
|
||||||
|
cy.wait('@signup');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After setup instance', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetState();
|
||||||
|
|
||||||
|
// インスタンス初期セットアップ
|
||||||
|
cy.registerUser('admin', 'pass', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||||
|
// waitを入れることでそれを防止できる
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signup', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/api/signup').as('signup');
|
||||||
|
|
||||||
|
cy.get('[data-cy-signup]').click();
|
||||||
|
cy.get('[data-cy-signup-username] input').type('alice');
|
||||||
|
cy.get('[data-cy-signup-password] input').type('alice1234');
|
||||||
|
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
|
||||||
|
cy.get('[data-cy-signup-submit]').click();
|
||||||
|
|
||||||
|
cy.wait('@signup');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After user signup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetState();
|
||||||
|
|
||||||
|
// インスタンス初期セットアップ
|
||||||
|
cy.registerUser('admin', 'pass', true);
|
||||||
|
|
||||||
|
// ユーザー作成
|
||||||
|
cy.registerUser('alice', 'alice1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||||
|
// waitを入れることでそれを防止できる
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signin', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/api/signin').as('signin');
|
||||||
|
|
||||||
|
cy.get('[data-cy-signin]').click();
|
||||||
|
cy.get('[data-cy-signin-username] input').type('alice');
|
||||||
|
// Enterキーでサインインできるかの確認も兼ねる
|
||||||
|
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
||||||
|
|
||||||
|
cy.wait('@signin');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suspend', function() {
|
||||||
|
cy.request('POST', '/api/admin/suspend-user', {
|
||||||
|
i: this.admin.token,
|
||||||
|
userId: this.alice.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.get('[data-cy-signin]').click();
|
||||||
|
cy.get('[data-cy-signin-username] input').type('alice');
|
||||||
|
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
||||||
|
|
||||||
|
// TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする
|
||||||
|
cy.contains(/アカウントが凍結されています|This account has been suspended due to/gi);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After user singed in', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetState();
|
||||||
|
|
||||||
|
// インスタンス初期セットアップ
|
||||||
|
cy.registerUser('admin', 'pass', true);
|
||||||
|
|
||||||
|
// ユーザー作成
|
||||||
|
cy.registerUser('alice', 'alice1234');
|
||||||
|
|
||||||
|
cy.login('alice', 'alice1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||||
|
// waitを入れることでそれを防止できる
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully loads', () => {
|
||||||
|
cy.get('[data-cy-open-post-form]').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('note', () => {
|
||||||
|
cy.get('[data-cy-open-post-form]').click();
|
||||||
|
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
||||||
|
cy.get('[data-cy-open-post-form-submit]').click();
|
||||||
|
|
||||||
|
cy.contains('Hello, Misskey!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: 投稿フォームの公開範囲指定のテスト
|
||||||
|
// TODO: 投稿フォームのファイル添付のテスト
|
||||||
|
// TODO: 投稿フォームのハッシュタグ保持フィールドのテスト
|
65
cypress/e2e/widgets.cy.js
Normal file
65
cypress/e2e/widgets.cy.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
describe('After user signed in', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetState();
|
||||||
|
cy.viewport('macbook-16');
|
||||||
|
|
||||||
|
// インスタンス初期セットアップ
|
||||||
|
cy.registerUser('admin', 'pass', true);
|
||||||
|
|
||||||
|
// ユーザー作成
|
||||||
|
cy.registerUser('alice', 'alice1234');
|
||||||
|
|
||||||
|
cy.login('alice', 'alice1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||||
|
// waitを入れることでそれを防止できる
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('widget edit toggle is visible', () => {
|
||||||
|
cy.get('.mk-widget-edit').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('widget select should be visible in edit mode', () => {
|
||||||
|
cy.get('.mk-widget-edit').click();
|
||||||
|
cy.get('.mk-widget-select').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('first widget should be removed', () => {
|
||||||
|
cy.get('.mk-widget-edit').click();
|
||||||
|
cy.get('.customize-container:first-child .remove._button').click();
|
||||||
|
cy.get('.customize-container').should('have.length', 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildWidgetTest(widgetName) {
|
||||||
|
it(`${widgetName} widget should get added`, () => {
|
||||||
|
cy.get('.mk-widget-edit').click();
|
||||||
|
cy.get('.mk-widget-select select').select(widgetName, { force: true });
|
||||||
|
cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true });
|
||||||
|
cy.get('.mk-widget-add').click({ force: true });
|
||||||
|
cy.get(`.mkw-${widgetName}`).should('exist');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildWidgetTest('memo');
|
||||||
|
buildWidgetTest('notifications');
|
||||||
|
buildWidgetTest('timeline');
|
||||||
|
buildWidgetTest('calendar');
|
||||||
|
buildWidgetTest('rss');
|
||||||
|
buildWidgetTest('trends');
|
||||||
|
buildWidgetTest('clock');
|
||||||
|
buildWidgetTest('activity');
|
||||||
|
buildWidgetTest('photos');
|
||||||
|
buildWidgetTest('digitalClock');
|
||||||
|
buildWidgetTest('federation');
|
||||||
|
buildWidgetTest('postForm');
|
||||||
|
buildWidgetTest('slideshow');
|
||||||
|
buildWidgetTest('serverMetric');
|
||||||
|
buildWidgetTest('onlineUsers');
|
||||||
|
buildWidgetTest('jobQueue');
|
||||||
|
buildWidgetTest('button');
|
||||||
|
buildWidgetTest('aiscript');
|
||||||
|
buildWidgetTest('aichan');
|
||||||
|
});
|
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
22
cypress/plugins/index.js
Normal file
22
cypress/plugins/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Cypress.PluginConfig}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
}
|
55
cypress/support/commands.js
Normal file
55
cypress/support/commands.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
|
Cypress.Commands.add('resetState', () => {
|
||||||
|
cy.window(win => {
|
||||||
|
win.indexedDB.deleteDatabase('keyval-store');
|
||||||
|
});
|
||||||
|
cy.request('POST', '/api/reset-db').as('reset');
|
||||||
|
cy.get('@reset').its('status').should('equal', 204);
|
||||||
|
cy.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
|
||||||
|
const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
|
||||||
|
|
||||||
|
cy.request('POST', route, {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}).its('body').as(username);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('login', (username, password) => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/api/signin').as('signin');
|
||||||
|
|
||||||
|
cy.get('[data-cy-signin]').click();
|
||||||
|
cy.get('[data-cy-signin-username] input').type(username);
|
||||||
|
cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
|
||||||
|
|
||||||
|
cy.wait('@signin').as('signedIn');
|
||||||
|
});
|
32
cypress/support/e2e.js
Normal file
32
cypress/support/e2e.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
|
|
||||||
|
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||||
|
if ([
|
||||||
|
// Chrome
|
||||||
|
'ResizeObserver loop limit exceeded',
|
||||||
|
|
||||||
|
// Firefox
|
||||||
|
'ResizeObserver loop completed with undelivered notifications',
|
||||||
|
].some(msg => err.message.includes(msg))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
52
docker-compose.yml
Normal file
52
docker-compose.yml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
restart: always
|
||||||
|
links:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
# - es
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
- external_network
|
||||||
|
volumes:
|
||||||
|
- ./files:/foundkey/files
|
||||||
|
- ./.config:/foundkey/.config:ro
|
||||||
|
|
||||||
|
redis:
|
||||||
|
restart: always
|
||||||
|
image: redis:7.0-alpine
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
volumes:
|
||||||
|
- ./redis:/data
|
||||||
|
|
||||||
|
db:
|
||||||
|
restart: always
|
||||||
|
image: postgres:14.5-alpine
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
env_file:
|
||||||
|
- .config/docker.env
|
||||||
|
volumes:
|
||||||
|
- ./db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
# es:
|
||||||
|
# restart: always
|
||||||
|
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
|
||||||
|
# environment:
|
||||||
|
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||||
|
# - "TAKE_FILE_OWNERSHIP=111"
|
||||||
|
# networks:
|
||||||
|
# - internal_network
|
||||||
|
# volumes:
|
||||||
|
# - ./elasticsearch:/usr/share/elasticsearch/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal_network:
|
||||||
|
internal: true
|
||||||
|
external_network:
|
|
@ -74,9 +74,6 @@ The fields of `Meta` are currently not used or checked when importing emoji, exc
|
||||||
For each `Emoji`:
|
For each `Emoji`:
|
||||||
- `downloaded`: should always be true. If the field is missing or not truthy, the emoji will not be imported.
|
- `downloaded`: should always be true. If the field is missing or not truthy, the emoji will not be imported.
|
||||||
- `fileName`: name of the image file inside the packed file.
|
- `fileName`: name of the image file inside the packed file.
|
||||||
The filename has to match the following ECMAScript RegExp: `/^[a-zA-Z0-9_]+?([a-zA-Z0-9\.]+)?$/`
|
|
||||||
(i.e. composed of latin letters, digits, underscores or dots, not starting with a dot and not ending with an underscore)
|
|
||||||
If the file does not match this RegExp, the respective emoji will not be imported!
|
|
||||||
- `emoji`: data associated with the emoji as it was stored in the database. Currently most of these fields are
|
- `emoji`: data associated with the emoji as it was stored in the database. Currently most of these fields are
|
||||||
not even checked for existence. The following are currently used:
|
not even checked for existence. The following are currently used:
|
||||||
- `name`: name of the emoji for the user, e.g. `blobfox` if a user should type in `:blobfox:` to get the emoji.
|
- `name`: name of the emoji for the user, e.g. `blobfox` if a user should type in `:blobfox:` to get the emoji.
|
||||||
|
|
85
docs/install-docker.md
Normal file
85
docs/install-docker.md
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Create FoundKey instance with Docker Compose
|
||||||
|
|
||||||
|
This guide describes how to install and setup FoundKey with Docker Compose.
|
||||||
|
|
||||||
|
**WARNING:**
|
||||||
|
Never change the domain name (hostname) of an instance once you start using it!
|
||||||
|
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- Docker or Podman
|
||||||
|
- Docker Compose plugin (or podman-compose)
|
||||||
|
|
||||||
|
If using Podman, replace `docker` with `podman`. Commands using `docker compose` should be replaced with `podman-compose`.
|
||||||
|
|
||||||
|
You may need to prefix `docker` commands with `sudo` unless your user is in the `docker` group or you are running Docker in rootless mode.
|
||||||
|
|
||||||
|
## Get the repository
|
||||||
|
```sh
|
||||||
|
git clone https://akkoma.dev/FoundKeyGang/FoundKey.git
|
||||||
|
cd FoundKey
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it easier to perform your own changes on top, we suggest making a branch based on the latest tag.
|
||||||
|
In this example, we'll use `v13.0.0-preview1` as the tag to check out and `my-branch` as the branch name.
|
||||||
|
```sh
|
||||||
|
git checkout tags/v13.0.0-preview1 -b my-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
Copy example configuration files with following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .config/docker_example.yml .config/default.yml
|
||||||
|
cp .config/docker_example.env .config/docker.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `default.yml` and `docker.env` according to the instructions in the files.
|
||||||
|
You will need to set the database host to `db` and Redis host to `redis` in order to use the internal container network for these services.
|
||||||
|
|
||||||
|
Edit `docker-compose.yml` if necessary. (e.g. if you want to change the port).
|
||||||
|
If you are using SELinux (eg. you're on Fedora or a RHEL derivative), you'll want to add the `Z` mount flag to the volume mounts to allow the containers to access the contents of those volumes.
|
||||||
|
|
||||||
|
Also check out the [Configure Foundkey](./install.md#configure-foundkey) section in the ordinary installation instructions.
|
||||||
|
|
||||||
|
## Build and initialize
|
||||||
|
The following command will build FoundKey and initialize the database.
|
||||||
|
This will take some time.
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
docker compose build
|
||||||
|
docker compose run --rm web yarn run init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Launch
|
||||||
|
You can start FoundKey with the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
In case you are encountering issues, you can run `docker compose logs -f` to get the log output of the running containers.
|
||||||
|
|
||||||
|
## How to update your FoundKey server
|
||||||
|
When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyGang/FoundKey/src/branch/main/CHANGELOG.md) to know in advance the changes and whether or not additional work is required (in most cases, it is not).
|
||||||
|
|
||||||
|
To update your branch to the latest tag (in this example `v13.0.0-preview2`), you can do the following:
|
||||||
|
```sh
|
||||||
|
git fetch -t
|
||||||
|
|
||||||
|
# Use --squash if you want to merge all of the changes in the tag into a single commit.
|
||||||
|
# Useful if you have made additional changes.
|
||||||
|
git merge tags/v13.0.0-preview2
|
||||||
|
|
||||||
|
# Rebuild and restart the docker container.
|
||||||
|
docker compose build
|
||||||
|
docker compose down && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
It may take some time depending on the contents of the update and the size of the database.
|
||||||
|
|
||||||
|
## How to execute CLI commands
|
||||||
|
```sh
|
||||||
|
docker compose run --rm web node packages/backend/built/tools/foo bar
|
||||||
|
```
|
|
@ -134,7 +134,7 @@ Run `NODE_ENV=production npm start` to launch FoundKey manually. To stop the ser
|
||||||
|
|
||||||
### Launch with systemd
|
### Launch with systemd
|
||||||
|
|
||||||
Run `systemctl edit --full --force foundkey.service`, and paste the following:
|
Run `systemctl --edit --full --force foundkey.service`, and paste the following:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
|
@ -144,7 +144,7 @@ Description=FoundKey daemon
|
||||||
Type=simple
|
Type=simple
|
||||||
User=foundkey
|
User=foundkey
|
||||||
ExecStart=/usr/bin/npm start
|
ExecStart=/usr/bin/npm start
|
||||||
WorkingDirectory=/home/foundkey/FoundKey
|
WorkingDirectory=/home/foundkey/foundkey
|
||||||
Environment="NODE_ENV=production"
|
Environment="NODE_ENV=production"
|
||||||
TimeoutSec=60
|
TimeoutSec=60
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
|
@ -178,7 +178,7 @@ command_args="start"
|
||||||
command_user="foundkey"
|
command_user="foundkey"
|
||||||
|
|
||||||
supervisor="supervise-daemon"
|
supervisor="supervise-daemon"
|
||||||
supervise_daemon_args=" -d /home/foundkey/FoundKey -e NODE_ENV=\"production\""
|
supervise_daemon_args=" -d /home/foundkey/foundkey -e NODE_ENV=\"production\""
|
||||||
|
|
||||||
pidfile="/run/${RC_SVCNAME}.pid"
|
pidfile="/run/${RC_SVCNAME}.pid"
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,10 @@ LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n nsfwDetection16
|
||||||
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)"
|
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)"
|
||||||
|
|
||||||
for i in $(seq 1 $NUM_MIGRATIONS); do
|
for i in $(seq 1 $NUM_MIGRATIONS); do
|
||||||
npx typeorm migration:revert -d ormconfig.js || continue
|
npx typeorm migration:revert -d ormconfig.js
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** TypeORM might hang when reverting a migration. If it says that the migration was reverted successfully, you can force close TypeORM using Ctrl-C. The script will continue until all of the migrations have been reverted.
|
|
||||||
|
|
||||||
## Switching repositories
|
## Switching repositories
|
||||||
To switch to the FoundKey repository, do the following in your Misskey install location:
|
To switch to the FoundKey repository, do the following in your Misskey install location:
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "أنشئ حسابًا"
|
||||||
save: "حفظ"
|
save: "حفظ"
|
||||||
users: "المستخدمون"
|
users: "المستخدمون"
|
||||||
addUser: "اضافة مستخدم"
|
addUser: "اضافة مستخدم"
|
||||||
|
favorite: "أضفها للمفضلة"
|
||||||
|
favorites: "المفضلات"
|
||||||
|
unfavorite: "إزالة من المفضلة"
|
||||||
pin: "دبّسها على الصفحة الشخصية"
|
pin: "دبّسها على الصفحة الشخصية"
|
||||||
unpin: "ألغ تدبيسها من ملفك الشخصي"
|
unpin: "ألغ تدبيسها من ملفك الشخصي"
|
||||||
copyContent: "انسخ المحتوى"
|
copyContent: "انسخ المحتوى"
|
||||||
|
@ -220,6 +223,7 @@ uploadFromUrl: "ارفع عبر رابط"
|
||||||
uploadFromUrlDescription: "رابط الملف المراد رفعه"
|
uploadFromUrlDescription: "رابط الملف المراد رفعه"
|
||||||
uploadFromUrlRequested: "الرفع مطلوب"
|
uploadFromUrlRequested: "الرفع مطلوب"
|
||||||
uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام الرفع"
|
uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام الرفع"
|
||||||
|
explore: "استكشاف"
|
||||||
messageRead: "مقروءة"
|
messageRead: "مقروءة"
|
||||||
noMoreHistory: "لا يوجد المزيد من التاريخ"
|
noMoreHistory: "لا يوجد المزيد من التاريخ"
|
||||||
startMessaging: "ابدأ محادثة"
|
startMessaging: "ابدأ محادثة"
|
||||||
|
@ -299,6 +303,9 @@ inMb: "بالميغابايت"
|
||||||
iconUrl: "رابط الأيقونة"
|
iconUrl: "رابط الأيقونة"
|
||||||
bannerUrl: "رابط صورة اللافتة"
|
bannerUrl: "رابط صورة اللافتة"
|
||||||
backgroundImageUrl: "رابط صورة الخلفية"
|
backgroundImageUrl: "رابط صورة الخلفية"
|
||||||
|
pinnedUsers: "المستخدمون المدبسون"
|
||||||
|
pinnedUsersDescription: "قائمة المستخدمين المدبسين في لسان \"استكشف\" ، اجعل كل اسم\
|
||||||
|
\ مستخدم في سطر لوحده."
|
||||||
hcaptchaSiteKey: "مفتاح الموقع"
|
hcaptchaSiteKey: "مفتاح الموقع"
|
||||||
hcaptchaSecretKey: "المفتاح السري"
|
hcaptchaSecretKey: "المفتاح السري"
|
||||||
recaptchaSiteKey: "مفتاح الموقع"
|
recaptchaSiteKey: "مفتاح الموقع"
|
||||||
|
@ -323,6 +330,11 @@ silence: "اكتم"
|
||||||
silenceConfirm: "أمتأكد من كتم هذا المستخدم؟"
|
silenceConfirm: "أمتأكد من كتم هذا المستخدم؟"
|
||||||
unsilence: "إلغاء الكتم"
|
unsilence: "إلغاء الكتم"
|
||||||
unsilenceConfirm: "أمتأكد من إلغاء كتم هذا المستخدم؟"
|
unsilenceConfirm: "أمتأكد من إلغاء كتم هذا المستخدم؟"
|
||||||
|
popularUsers: "المستخدمون الرائدون"
|
||||||
|
recentlyUpdatedUsers: "أصحاب النشاطات الأخيرة"
|
||||||
|
recentlyRegisteredUsers: "المستخدمون المنضمون حديثًا"
|
||||||
|
recentlyDiscoveredUsers: "المستخدمون المكتشفون حديثًا"
|
||||||
|
popularTags: "الوسوم الرائجة"
|
||||||
userList: "القوائم"
|
userList: "القوائم"
|
||||||
aboutMisskey: "عن FoundKey"
|
aboutMisskey: "عن FoundKey"
|
||||||
administrator: "المدير"
|
administrator: "المدير"
|
||||||
|
@ -363,6 +375,7 @@ messagingWithGroup: "محادثة جماعية"
|
||||||
title: "العنوان"
|
title: "العنوان"
|
||||||
text: "النص"
|
text: "النص"
|
||||||
enable: "تشغيل"
|
enable: "تشغيل"
|
||||||
|
next: "التالية"
|
||||||
retype: "أعد الكتابة"
|
retype: "أعد الكتابة"
|
||||||
noteOf: "ملاحظات {user}"
|
noteOf: "ملاحظات {user}"
|
||||||
inviteToGroup: "دعوة إلى فريق"
|
inviteToGroup: "دعوة إلى فريق"
|
||||||
|
@ -523,6 +536,7 @@ abuseReports: "البلاغات"
|
||||||
reportAbuse: "أبلغ"
|
reportAbuse: "أبلغ"
|
||||||
reportAbuseOf: "أبلغ عن {name}"
|
reportAbuseOf: "أبلغ عن {name}"
|
||||||
fillAbuseReportDescription: "أكتب بالتفصيل سبب البلاغ"
|
fillAbuseReportDescription: "أكتب بالتفصيل سبب البلاغ"
|
||||||
|
abuseReported: "أُرسل البلاغ، شكرًا لك"
|
||||||
reporter: "المُبلّغ"
|
reporter: "المُبلّغ"
|
||||||
reporteeOrigin: "أصل البلاغ"
|
reporteeOrigin: "أصل البلاغ"
|
||||||
reporterOrigin: "أصل المُبلّغ"
|
reporterOrigin: "أصل المُبلّغ"
|
||||||
|
@ -567,6 +581,7 @@ loadRawImages: "حمّل الصور الأصلية بدلًا من المصغر
|
||||||
disableShowingAnimatedImages: "لا تشغّل الصور المتحركة"
|
disableShowingAnimatedImages: "لا تشغّل الصور المتحركة"
|
||||||
verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق."
|
verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق."
|
||||||
emailVerified: "تُحقّق من بريدك الإلكتروني"
|
emailVerified: "تُحقّق من بريدك الإلكتروني"
|
||||||
|
noteFavoritesCount: "عدد الملاحظات المفضلة"
|
||||||
pageLikesCount: "عدد الصفحات التي أعجبت بها"
|
pageLikesCount: "عدد الصفحات التي أعجبت بها"
|
||||||
pageLikedCount: "عدد صفحاتك المُعجب بها"
|
pageLikedCount: "عدد صفحاتك المُعجب بها"
|
||||||
contact: "التواصل"
|
contact: "التواصل"
|
||||||
|
@ -870,6 +885,35 @@ _time:
|
||||||
minute: "د"
|
minute: "د"
|
||||||
hour: "سا"
|
hour: "سا"
|
||||||
day: "ي"
|
day: "ي"
|
||||||
|
_tutorial:
|
||||||
|
title: "كيف تستخدم FoundKey"
|
||||||
|
step1_1: "مرحبًا!"
|
||||||
|
step1_2: "تدعى هذه الصفحة 'الخيط الزمني' وهي تحوي ملاحظات الأشخاص الذي تتابعهم مرتبة\
|
||||||
|
\ حسب تاريخ نشرها."
|
||||||
|
step1_3: "خيطك الزمني فارغ حاليًا بما أنك لا تتابع أي شخص ولم تنشر أي ملاحظة."
|
||||||
|
step2_1: "لننهي إعداد ملفك الشخصي قبل كتابة ملاحظة أو متابعة أشخاص."
|
||||||
|
step2_2: "أعطاء معلومات عن شخصيتك يمنح من له نفس إهتماماتك فرصة متابعتك والتفاعل\
|
||||||
|
\ مع ملاحظاتك."
|
||||||
|
step3_1: "هل أنهيت إعداد حسابك؟"
|
||||||
|
step3_2: "إذا تاليًا لتنشر ملاحظة. أنقر على أيقونة القلم في أعلى الشاشة"
|
||||||
|
step3_3: "املأ النموذج وانقر الزرّ الموجود في أعلى اليمين للإرسال."
|
||||||
|
step3_4: "ليس لديك ما تقوله؟ إذا اكتب \"بدأتُ استخدم ميسكي\"."
|
||||||
|
step4_1: "هل نشرت ملاحظتك الأولى؟"
|
||||||
|
step4_2: "مرحى! يمكنك الآن رؤية ملاحظتك في الخيط الزمني."
|
||||||
|
step5_1: "والآن، لنجعل الخيط الزمني أكثر حيوية وذلك بمتابعة بعض المستخدمين."
|
||||||
|
step5_2: "تعرض صفحة {features} الملاحظات المتداولة في هذا المثيل ويتيح لك {Explore}\
|
||||||
|
\ العثور على المستخدمين الرائدين. اعثر على الأشخاص الذين يثيرون إهتمامك وتابعهم!"
|
||||||
|
step5_3: "لمتابعة مستخدمين ادخل ملفهم الشخصي بالنقر على صورتهم الشخصية ثم اضغط زر\
|
||||||
|
\ 'تابع'."
|
||||||
|
step5_4: "إذا كان لدى المستخدم رمز قفل بجوار اسمه ، وجب عليك انتظاره ليقبل طلب المتابعة\
|
||||||
|
\ يدويًا."
|
||||||
|
step6_1: "الآن ستتمكن من رؤية ملاحظات المستخدمين المتابَعين في الخيط الزمني."
|
||||||
|
step6_2: "يمكنك التفاعل بسرعة مع الملاحظات عن طريق إضافة \"تفاعل\"."
|
||||||
|
step6_3: "لإضافة تفاعل لملاحظة ، انقر فوق علامة \"+\" أسفل للملاحظة واختر الإيموجي\
|
||||||
|
\ المطلوب."
|
||||||
|
step7_1: "مبارك ! أنهيت الدورة التعليمية الأساسية لاستخدام ميسكي."
|
||||||
|
step7_2: "إذا أردت معرفة المزيد عن ميسكي زر {help}."
|
||||||
|
step7_3: "حظًا سعيدًا واستمتع بوقتك مع ميسكي! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "سجلت سلفًا جهازًا للاستيثاق بعاملين."
|
alreadyRegistered: "سجلت سلفًا جهازًا للاستيثاق بعاملين."
|
||||||
registerDevice: "سجّل جهازًا جديدًا"
|
registerDevice: "سجّل جهازًا جديدًا"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "নিবন্ধন করুন"
|
||||||
save: "সংরক্ষণ"
|
save: "সংরক্ষণ"
|
||||||
users: "ব্যবহারকারীগণ"
|
users: "ব্যবহারকারীগণ"
|
||||||
addUser: "ব্যবহারকারী যোগ করুন"
|
addUser: "ব্যবহারকারী যোগ করুন"
|
||||||
|
favorite: "পছন্দ"
|
||||||
|
favorites: "পছন্দগুলি"
|
||||||
|
unfavorite: "পছন্দ না"
|
||||||
pin: "পিন করা"
|
pin: "পিন করা"
|
||||||
unpin: "পিন সরান"
|
unpin: "পিন সরান"
|
||||||
copyContent: "বিষয়বস্তু কপি করুন"
|
copyContent: "বিষয়বস্তু কপি করুন"
|
||||||
|
@ -234,6 +237,7 @@ uploadFromUrl: "URL হতে আপলোড"
|
||||||
uploadFromUrlDescription: "যে ফাইলটি আপলোড করতে চান, সেটির URL"
|
uploadFromUrlDescription: "যে ফাইলটি আপলোড করতে চান, সেটির URL"
|
||||||
uploadFromUrlRequested: "আপলোড অনুরোধ করা হয়েছে"
|
uploadFromUrlRequested: "আপলোড অনুরোধ করা হয়েছে"
|
||||||
uploadFromUrlMayTakeTime: "URL হতে আপলোড হতে কিছু সময় লাগতে পারে।"
|
uploadFromUrlMayTakeTime: "URL হতে আপলোড হতে কিছু সময় লাগতে পারে।"
|
||||||
|
explore: "ঘুরে দেখুন"
|
||||||
messageRead: "পড়া"
|
messageRead: "পড়া"
|
||||||
noMoreHistory: "আর কোন ইতিহাস নেই"
|
noMoreHistory: "আর কোন ইতিহাস নেই"
|
||||||
startMessaging: "চ্যাট শুরু করুন"
|
startMessaging: "চ্যাট শুরু করুন"
|
||||||
|
@ -314,6 +318,9 @@ inMb: "মেগাবাইটে লিখুন"
|
||||||
iconUrl: "আইকনের URL (ফ্যাভিকন, ইত্যাদি)"
|
iconUrl: "আইকনের URL (ফ্যাভিকন, ইত্যাদি)"
|
||||||
bannerUrl: "ব্যানার ছবির URL"
|
bannerUrl: "ব্যানার ছবির URL"
|
||||||
backgroundImageUrl: "পটভূমির চিত্রের URL"
|
backgroundImageUrl: "পটভূমির চিত্রের URL"
|
||||||
|
pinnedUsers: "পিন করা ব্যাবহারকারীগণ"
|
||||||
|
pinnedUsersDescription: "আপনি যেসব ব্যবহারকারীদের \"ঘুরে দেখুন\" পৃষ্ঠায় পিন করতে\
|
||||||
|
\ চান তাদের বর্ণনা করুন, প্রত্যেকের বর্ণনা আলাদা লাইনে লিখুন"
|
||||||
hcaptchaSiteKey: "সাইট কী"
|
hcaptchaSiteKey: "সাইট কী"
|
||||||
hcaptchaSecretKey: "সিক্রেট কী"
|
hcaptchaSecretKey: "সিক্রেট কী"
|
||||||
recaptchaSiteKey: "সাইট কী"
|
recaptchaSiteKey: "সাইট কী"
|
||||||
|
@ -338,6 +345,11 @@ silence: "নীরব"
|
||||||
silenceConfirm: "আপনি কি এই ব্যাবহারকারীকের নীরব করতে চান?"
|
silenceConfirm: "আপনি কি এই ব্যাবহারকারীকের নীরব করতে চান?"
|
||||||
unsilence: "সরব"
|
unsilence: "সরব"
|
||||||
unsilenceConfirm: "আপনি কি এই ব্যাবহারকারীকের সরব করতে চান?"
|
unsilenceConfirm: "আপনি কি এই ব্যাবহারকারীকের সরব করতে চান?"
|
||||||
|
popularUsers: "জনপ্রিয় ব্যবহারকারীগন"
|
||||||
|
recentlyUpdatedUsers: "সম্প্রতি পোস্ট করা ব্যবহারকারীগন"
|
||||||
|
recentlyRegisteredUsers: "নতুন যোগ দেওয়া ব্যবহারকারীগন"
|
||||||
|
recentlyDiscoveredUsers: "নতুন খুঁজে পাওয়া ব্যবহারকারীগন"
|
||||||
|
popularTags: "জনপ্রিয় ট্যাগগুলি"
|
||||||
userList: "লিস্ট"
|
userList: "লিস্ট"
|
||||||
aboutMisskey: "FoundKey সম্পর্কে"
|
aboutMisskey: "FoundKey সম্পর্কে"
|
||||||
administrator: "প্রশাসক"
|
administrator: "প্রশাসক"
|
||||||
|
@ -378,6 +390,7 @@ messagingWithGroup: "গ্রুপ চ্যাট"
|
||||||
title: "শিরোনাম"
|
title: "শিরোনাম"
|
||||||
text: "পাঠ্য"
|
text: "পাঠ্য"
|
||||||
enable: "সক্রিয়"
|
enable: "সক্রিয়"
|
||||||
|
next: "পরবর্তী"
|
||||||
retype: "পুনঃ প্রবেশ"
|
retype: "পুনঃ প্রবেশ"
|
||||||
noteOf: "{user} এর নোট"
|
noteOf: "{user} এর নোট"
|
||||||
inviteToGroup: "গ্রুপে আমন্ত্রণ জানান"
|
inviteToGroup: "গ্রুপে আমন্ত্রণ জানান"
|
||||||
|
@ -569,6 +582,7 @@ abuseReports: "অভিযোগ"
|
||||||
reportAbuse: "অভিযোগ"
|
reportAbuse: "অভিযোগ"
|
||||||
reportAbuseOf: "{name} এ অভিযোগ করুন"
|
reportAbuseOf: "{name} এ অভিযোগ করুন"
|
||||||
fillAbuseReportDescription: "রিপোর্টের কারণ বর্ণনা করুন."
|
fillAbuseReportDescription: "রিপোর্টের কারণ বর্ণনা করুন."
|
||||||
|
abuseReported: "আপনার অভিযোগটি দাখিল করা হয়েছে। আপনাকে ধন্যবাদ।"
|
||||||
reporter: "অভিযোগকারী"
|
reporter: "অভিযোগকারী"
|
||||||
reporteeOrigin: "অভিযোগটির উৎস"
|
reporteeOrigin: "অভিযোগটির উৎস"
|
||||||
reporterOrigin: "অভিযোগকারীর উৎস"
|
reporterOrigin: "অভিযোগকারীর উৎস"
|
||||||
|
@ -619,6 +633,7 @@ disableShowingAnimatedImages: "অ্যানিমেটেড চিত্র
|
||||||
verificationEmailSent: "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে। সেটআপ সম্পূর্ণ করতে ইমেল এর\
|
verificationEmailSent: "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে। সেটআপ সম্পূর্ণ করতে ইমেল এর\
|
||||||
\ লিঙ্ক অনুসরণ করুন।"
|
\ লিঙ্ক অনুসরণ করুন।"
|
||||||
emailVerified: "ইমেইল নিশ্চিত করা হয়েছে"
|
emailVerified: "ইমেইল নিশ্চিত করা হয়েছে"
|
||||||
|
noteFavoritesCount: "পছন্দ করা নোটের সংখ্যা"
|
||||||
pageLikesCount: "পেজ লাইক করেছেন"
|
pageLikesCount: "পেজ লাইক করেছেন"
|
||||||
pageLikedCount: "পেজ লাইক পেয়েছেন"
|
pageLikedCount: "পেজ লাইক পেয়েছেন"
|
||||||
contact: "পরিচিতি সমূহ"
|
contact: "পরিচিতি সমূহ"
|
||||||
|
@ -964,6 +979,41 @@ _time:
|
||||||
minute: "মিনিট"
|
minute: "মিনিট"
|
||||||
hour: "ঘণ্টা"
|
hour: "ঘণ্টা"
|
||||||
day: "দিন"
|
day: "দিন"
|
||||||
|
_tutorial:
|
||||||
|
title: "FoundKey কিভাবে ব্যাবহার করবেন"
|
||||||
|
step1_1: "স্বাগতম!"
|
||||||
|
step1_2: "এই স্ক্রীনটিকে \"টাইমলাইন\" বলা হয় এবং কালানুক্রমিক ক্রমে আপনার এবং আপনি\
|
||||||
|
\ যাদের \"অনুসরণ করেন\" তাদের \"নোটগুলি\" দেখায়৷"
|
||||||
|
step1_3: "আপনি আপনার টাইমলাইনে কিছু দেখতে পাবেন না কারণ আপনি এখনও কোনো নোট পোস্ট\
|
||||||
|
\ করেননি এবং আপনি কাউকে অনুসরণ করছেন না৷"
|
||||||
|
step2_1: "নোট তৈরি করার আগে বা কাউকে অনুসরণ করার আগে প্রথমে আপনার প্রোফাইলটি সম্পূর্ণ\
|
||||||
|
\ করুন।"
|
||||||
|
step2_2: "আপনি কে তা জানা অনেক লোকের জন্য আপনার নোটগুলি দেখা এবং অনুসরণ করাকে সহজ\
|
||||||
|
\ করে তোলে৷"
|
||||||
|
step3_1: "আপনি কি সফলভাবে আপনার প্রোফাইল সেট আপ করেছেন?"
|
||||||
|
step3_2: "এখন, কিছু নোট পোস্ট করার চেষ্টা করুন। পোস্ট ফর্ম খুলতে পেন্সিল চিহ্নযুক্ত\
|
||||||
|
\ বাটনে ক্লিক করুন।"
|
||||||
|
step3_3: "বিষয়বস্তু লেখার পরে, আপনি ফর্মের উপরের ডানদিকের বাটনে ক্লিক করে পোস্ট\
|
||||||
|
\ করতে পারেন।"
|
||||||
|
step3_4: "পোস্ট করার মত কিছু মনে পরছে না? \"আমি মিসকি সেট আপ করছি\" বললে কেমন হয়?"
|
||||||
|
step4_1: "পোস্ট করেছেন?"
|
||||||
|
step4_2: "সাবাশ! এখন আপনার নোট টাইমলাইনে দেখা যাবে।"
|
||||||
|
step5_1: "এখন অন্যদেরকে অনুসরণ করে আপনার টাইমলাইনকে প্রাণবন্ত করে তুলুন।"
|
||||||
|
step5_2: "আপনি {featured}-এ জনপ্রিয় নোটগুলি দেখতে পারেন, যাতে আপনি যে ব্যক্তিকে\
|
||||||
|
\ পছন্দ করেন তাকে বেছে নিতে এবং অনুসরণ করতে পারেন, অথবা {explore}-এ জনপ্রিয় ব্যবহারকারীদের\
|
||||||
|
\ দেখতে পারেন৷"
|
||||||
|
step5_3: "একজন ব্যবহারকারীকে অনুসরণ করতে, ব্যবহারকারীর আইকনে ক্লিক করুন এবং ব্যবহারকারীর\
|
||||||
|
\ পৃষ্ঠাতে \"অনুসরণ করুন\" বাটনে ক্লিক করুন।"
|
||||||
|
step5_4: "যদি ব্যবহারকারীর নামের পাশে একটি লক আইকন থাকে তাহলে আপনার অনুসরণের অনুরোধ\
|
||||||
|
\ গ্রহণ করার জন্য তারা কিছু সময় নিতে পারে।"
|
||||||
|
step6_1: "সবকিছু ঠিক থাকলে আপনি টাইমলাইনে অন্য ব্যবহারকারীদের নোট দেখতে পাবেন।"
|
||||||
|
step6_2: "আপনি সহজেই আপনার প্রতিক্রিয়া জানাতে অন্য ব্যক্তির নোটে \"রিঅ্যাকশন\"\
|
||||||
|
\ যোগ করতে পারেন।"
|
||||||
|
step6_3: "একটি রিঅ্যাকশন যোগ করতে, নোটে \"+\" চিহ্নে ক্লিক করুন এবং আপনার পছন্দের\
|
||||||
|
\ রিঅ্যাকশন নির্বাচন করুন।"
|
||||||
|
step7_1: "অভিনন্দন! আপনি এখন FoundKey-র প্রাথমিক টিউটোরিয়ালটি শেষ করেছেন।"
|
||||||
|
step7_2: "আপনি যদি FoundKey সম্পর্কে আরও জানতে চান, তাহলে {help} এ দেখুন।"
|
||||||
|
step7_3: "এখন FoundKey উপভোগ করুন \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "আপনি ইতিমধ্যে একটি 2-ফ্যাক্টর অথেনটিকেশন ডিভাইস নিবন্ধন করেছেন৷"
|
alreadyRegistered: "আপনি ইতিমধ্যে একটি 2-ফ্যাক্টর অথেনটিকেশন ডিভাইস নিবন্ধন করেছেন৷"
|
||||||
registerDevice: "নতুন ডিভাইস নিবন্ধন করুন"
|
registerDevice: "নতুন ডিভাইস নিবন্ধন করুন"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registrar-se"
|
||||||
save: "Desar"
|
save: "Desar"
|
||||||
users: "Usuaris"
|
users: "Usuaris"
|
||||||
addUser: "Afegir un usuari"
|
addUser: "Afegir un usuari"
|
||||||
|
favorite: "Afegir a preferits"
|
||||||
|
favorites: "Favorits"
|
||||||
|
unfavorite: "Eliminar dels preferits"
|
||||||
pin: "Fixar al perfil"
|
pin: "Fixar al perfil"
|
||||||
unpin: "Para de fixar del perfil"
|
unpin: "Para de fixar del perfil"
|
||||||
copyContent: "Copiar el contingut"
|
copyContent: "Copiar el contingut"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registrace"
|
||||||
save: "Uložit"
|
save: "Uložit"
|
||||||
users: "Uživatelé"
|
users: "Uživatelé"
|
||||||
addUser: "Přidat uživatele"
|
addUser: "Přidat uživatele"
|
||||||
|
favorite: "Oblíbené"
|
||||||
|
favorites: "Oblíbené"
|
||||||
|
unfavorite: "Odebrat z oblízených"
|
||||||
pin: "Připnout"
|
pin: "Připnout"
|
||||||
unpin: "Odepnout"
|
unpin: "Odepnout"
|
||||||
copyContent: "Zkopírovat obsah"
|
copyContent: "Zkopírovat obsah"
|
||||||
|
@ -210,6 +213,7 @@ fromUrl: "Z URL"
|
||||||
uploadFromUrl: "Nahrát z URL adresy"
|
uploadFromUrl: "Nahrát z URL adresy"
|
||||||
uploadFromUrlDescription: "URL adresa souboru, který chcete nahrát"
|
uploadFromUrlDescription: "URL adresa souboru, který chcete nahrát"
|
||||||
uploadFromUrlMayTakeTime: "Může trvat nějakou dobu, dokud nebude dokončeno nahrávání."
|
uploadFromUrlMayTakeTime: "Může trvat nějakou dobu, dokud nebude dokončeno nahrávání."
|
||||||
|
explore: "Objevovat"
|
||||||
messageRead: "Přečtené"
|
messageRead: "Přečtené"
|
||||||
noMoreHistory: "To je vše"
|
noMoreHistory: "To je vše"
|
||||||
startMessaging: "Zahájit chat"
|
startMessaging: "Zahájit chat"
|
||||||
|
@ -280,6 +284,7 @@ inMb: "V megabajtech"
|
||||||
iconUrl: "Favicon URL"
|
iconUrl: "Favicon URL"
|
||||||
bannerUrl: "Baner URL"
|
bannerUrl: "Baner URL"
|
||||||
backgroundImageUrl: "Adresa URL obrázku pozadí"
|
backgroundImageUrl: "Adresa URL obrázku pozadí"
|
||||||
|
pinnedUsers: "Připnutí uživatelé"
|
||||||
hcaptchaSecretKey: "Tajný Klíč (Secret Key)"
|
hcaptchaSecretKey: "Tajný Klíč (Secret Key)"
|
||||||
recaptchaSecretKey: "Tajný Klíč (Secret Key)"
|
recaptchaSecretKey: "Tajný Klíč (Secret Key)"
|
||||||
antennas: "Antény"
|
antennas: "Antény"
|
||||||
|
@ -288,6 +293,7 @@ name: "Jméno"
|
||||||
antennaSource: "Zdroj Antény"
|
antennaSource: "Zdroj Antény"
|
||||||
caseSensitive: "Rozlišuje malá a velká písmena"
|
caseSensitive: "Rozlišuje malá a velká písmena"
|
||||||
connectedTo: "Následující účty jsou připojeny"
|
connectedTo: "Následující účty jsou připojeny"
|
||||||
|
popularTags: "Populární tagy"
|
||||||
userList: "Seznamy"
|
userList: "Seznamy"
|
||||||
aboutMisskey: "O FoundKey"
|
aboutMisskey: "O FoundKey"
|
||||||
administrator: "Administrátor"
|
administrator: "Administrátor"
|
||||||
|
@ -325,6 +331,7 @@ transfer: "Převod"
|
||||||
title: "Titulek"
|
title: "Titulek"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Povolit"
|
enable: "Povolit"
|
||||||
|
next: "Další"
|
||||||
retype: "Zadejte znovu"
|
retype: "Zadejte znovu"
|
||||||
noteOf: "{user} poznámky"
|
noteOf: "{user} poznámky"
|
||||||
inviteToGroup: "Pozvat do skupiny"
|
inviteToGroup: "Pozvat do skupiny"
|
||||||
|
|
|
@ -33,6 +33,9 @@ signup: "Registrieren"
|
||||||
save: "Speichern"
|
save: "Speichern"
|
||||||
users: "Benutzer"
|
users: "Benutzer"
|
||||||
addUser: "Benutzer hinzufügen"
|
addUser: "Benutzer hinzufügen"
|
||||||
|
favorite: "Zu Favoriten hinzufügen"
|
||||||
|
favorites: "Favoriten"
|
||||||
|
unfavorite: "Aus Favoriten entfernen"
|
||||||
pin: "An dein Profil anheften"
|
pin: "An dein Profil anheften"
|
||||||
unpin: "Von deinem Profil lösen"
|
unpin: "Von deinem Profil lösen"
|
||||||
copyContent: "Inhalt kopieren"
|
copyContent: "Inhalt kopieren"
|
||||||
|
@ -245,6 +248,7 @@ uploadFromUrlDescription: "URL der hochzuladenden Datei"
|
||||||
uploadFromUrlRequested: "Upload angefordert"
|
uploadFromUrlRequested: "Upload angefordert"
|
||||||
uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschlossen\
|
uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschlossen\
|
||||||
\ ist."
|
\ ist."
|
||||||
|
explore: "Erkunden"
|
||||||
messageRead: "Gelesen"
|
messageRead: "Gelesen"
|
||||||
noMoreHistory: "Kein weiterer Verlauf vorhanden"
|
noMoreHistory: "Kein weiterer Verlauf vorhanden"
|
||||||
startMessaging: "Neuen Chat erstellen"
|
startMessaging: "Neuen Chat erstellen"
|
||||||
|
@ -327,6 +331,9 @@ inMb: "In Megabytes"
|
||||||
iconUrl: "Icon-URL (favicon etc)"
|
iconUrl: "Icon-URL (favicon etc)"
|
||||||
bannerUrl: "Banner-URL"
|
bannerUrl: "Banner-URL"
|
||||||
backgroundImageUrl: "Hintergrundbild-URL"
|
backgroundImageUrl: "Hintergrundbild-URL"
|
||||||
|
pinnedUsers: "Angeheftete Benutzer"
|
||||||
|
pinnedUsersDescription: "Gib durch Leerzeichen getrennte Benutzer an, die an die \"\
|
||||||
|
Erkunden\"-Seite angeheftet werden sollen."
|
||||||
hcaptchaSiteKey: "Site key"
|
hcaptchaSiteKey: "Site key"
|
||||||
hcaptchaSecretKey: "Geheimer Schlüssel"
|
hcaptchaSecretKey: "Geheimer Schlüssel"
|
||||||
recaptchaSiteKey: "Site-Schlüssel"
|
recaptchaSiteKey: "Site-Schlüssel"
|
||||||
|
@ -353,6 +360,11 @@ silenceConfirm: "Möchtest du diesen Benutzer wirklich instanzweit stummschalten
|
||||||
unsilence: "Instanzweite Stummschaltung aufheben"
|
unsilence: "Instanzweite Stummschaltung aufheben"
|
||||||
unsilenceConfirm: "Möchtest du die instanzweite Stummschaltung dieses Benutzers wirklich\
|
unsilenceConfirm: "Möchtest du die instanzweite Stummschaltung dieses Benutzers wirklich\
|
||||||
\ aufheben?"
|
\ aufheben?"
|
||||||
|
popularUsers: "Beliebte Benutzer"
|
||||||
|
recentlyUpdatedUsers: "Vor kurzem aktive Benutzer"
|
||||||
|
recentlyRegisteredUsers: "Vor kurzem registrierte Benutzer"
|
||||||
|
recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer"
|
||||||
|
popularTags: "Beliebte Schlagwörter"
|
||||||
userList: "Liste"
|
userList: "Liste"
|
||||||
aboutMisskey: "Über FoundKey"
|
aboutMisskey: "Über FoundKey"
|
||||||
administrator: "Administrator"
|
administrator: "Administrator"
|
||||||
|
@ -393,6 +405,7 @@ messagingWithGroup: "Gruppenchat"
|
||||||
title: "Titel"
|
title: "Titel"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Aktivieren"
|
enable: "Aktivieren"
|
||||||
|
next: "Weiter"
|
||||||
retype: "Erneut eingeben"
|
retype: "Erneut eingeben"
|
||||||
noteOf: "Notiz von {user}"
|
noteOf: "Notiz von {user}"
|
||||||
inviteToGroup: "Zu Gruppe einladen"
|
inviteToGroup: "Zu Gruppe einladen"
|
||||||
|
@ -590,6 +603,7 @@ reportAbuse: "Melden"
|
||||||
reportAbuseOf: "{name} melden"
|
reportAbuseOf: "{name} melden"
|
||||||
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung\
|
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung\
|
||||||
\ an."
|
\ an."
|
||||||
|
abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
|
||||||
reporter: "Melder"
|
reporter: "Melder"
|
||||||
reporteeOrigin: "Herkunft des Gemeldeten"
|
reporteeOrigin: "Herkunft des Gemeldeten"
|
||||||
reporterOrigin: "Herkunft des Meldenden"
|
reporterOrigin: "Herkunft des Meldenden"
|
||||||
|
@ -640,6 +654,7 @@ disableShowingAnimatedImages: "Animierte Bilder nicht abspielen"
|
||||||
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet.\
|
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet.\
|
||||||
\ Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
|
\ Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
|
||||||
emailVerified: "Email-Adresse bestätigt"
|
emailVerified: "Email-Adresse bestätigt"
|
||||||
|
noteFavoritesCount: "Anzahl an als Favorit markierter Notizen"
|
||||||
pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten"
|
pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten"
|
||||||
pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten"
|
pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten"
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
@ -805,7 +820,6 @@ _ffVisibility:
|
||||||
public: "Öffentlich"
|
public: "Öffentlich"
|
||||||
followers: "Nur für Follower sichtbar"
|
followers: "Nur für Follower sichtbar"
|
||||||
private: "Privat"
|
private: "Privat"
|
||||||
nobody: Niemand (auch nicht du)
|
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "Fast geschafft"
|
almostThere: "Fast geschafft"
|
||||||
emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich\
|
emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich\
|
||||||
|
@ -1003,6 +1017,44 @@ _time:
|
||||||
minute: "Minute(n)"
|
minute: "Minute(n)"
|
||||||
hour: "Stunde(n)"
|
hour: "Stunde(n)"
|
||||||
day: "Tag(en)"
|
day: "Tag(en)"
|
||||||
|
_tutorial:
|
||||||
|
title: "Wie du FoundKey verwendest"
|
||||||
|
step1_1: "Willkommen!"
|
||||||
|
step1_2: "Diese Seite ist die „Chronik“. Sie zeigt dir deine geschrieben „Notizen“\
|
||||||
|
\ sowie die aller Benutzer, denen du „folgst“, in chronologischer Reihenfolge."
|
||||||
|
step1_3: "Deine Chronik sollte momentan leer sein, da du bis jetzt noch keine Notizen\
|
||||||
|
\ geschrieben hast und auch noch keinen Benutzern folgst."
|
||||||
|
step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst\
|
||||||
|
\ oder jemandem folgst."
|
||||||
|
step2_2: "Informationen darüber, was für eine Person du bist, macht es anderen leichter\
|
||||||
|
\ zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten."
|
||||||
|
step3_1: "Mit dem Einrichten deines Profils fertig?"
|
||||||
|
step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst\
|
||||||
|
\ du tun, indem du auf den Knopf mit dem Stift-Icon auf dem Bildschirm drückst."
|
||||||
|
step3_3: "Fülle das Fenster aus und drücke auf den Knopf oben rechts zum Senden."
|
||||||
|
step3_4: "Fällt dir nichts ein, das du schreiben möchtest? Versuch's mit \"Hallo\
|
||||||
|
\ FoundKey!\""
|
||||||
|
step4_1: "Fertig mit dem Senden deiner ersten Notiz?"
|
||||||
|
step4_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig\
|
||||||
|
\ gemacht."
|
||||||
|
step5_1: "Lass uns nun deiner Chronik etwas mehr Leben einhauchen, indem du einigen\
|
||||||
|
\ anderen Benutzern folgst."
|
||||||
|
step5_2: "{featured} zeigt dir beliebte Notizen dieser Instanz. In {explore} kannst\
|
||||||
|
\ du beliebte Benutzer finden. Schau dort, ob du Benutzer findest, die dich interessieren!"
|
||||||
|
step5_3: "Klicke zum Anzeigen des Profils eines Benutzers auf dessen Profilbild\
|
||||||
|
\ und dann auf den \"Folgen\"-Knopf, um diesem zu folgen."
|
||||||
|
step5_4: "Je nach Benutzer kann es etwas Zeit in Anspruch nehmen, bis dieser deine\
|
||||||
|
\ Follow-Anfrage bestätigt."
|
||||||
|
step6_1: "Wenn du nun auch die Notizen anderer Benutzer in deiner Chronik siehst,\
|
||||||
|
\ hast du auch diesmal alles richtig gemacht."
|
||||||
|
step6_2: "Du kannst ebenso „Reaktionen“ verwenden, um schnell auf Notizen anderer\
|
||||||
|
\ Benutzer zu reagieren."
|
||||||
|
step6_3: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol in der Notiz und\
|
||||||
|
\ wähle ein Emoji aus, mit dem du reagieren möchtest."
|
||||||
|
step7_1: "Glückwunsch! Du hast die Einführung in die Verwendung von FoundKey abgeschlossen."
|
||||||
|
step7_2: "Wenn du mehr über FoundKey lernen möchtest, schau dich im {help}-Bereich\
|
||||||
|
\ um."
|
||||||
|
step7_3: "Und nun, viel Spaß mit FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung\
|
alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung\
|
||||||
\ registriert."
|
\ registriert."
|
||||||
|
@ -1260,7 +1312,6 @@ _notification:
|
||||||
groupInvited: "Erhaltene Gruppeneinladungen"
|
groupInvited: "Erhaltene Gruppeneinladungen"
|
||||||
app: "Benachrichtigungen von Apps"
|
app: "Benachrichtigungen von Apps"
|
||||||
move: Account-Umzüge
|
move: Account-Umzüge
|
||||||
update: Beobachtete Notiz wurde bearbeitet
|
|
||||||
_actions:
|
_actions:
|
||||||
followBack: "folgt dir nun auch"
|
followBack: "folgt dir nun auch"
|
||||||
reply: "Antworten"
|
reply: "Antworten"
|
||||||
|
@ -1330,7 +1381,7 @@ addTag: Schlagwörter hinzufügen
|
||||||
removeTag: Schlagwörter entfernen
|
removeTag: Schlagwörter entfernen
|
||||||
exportAll: Alle exportieren
|
exportAll: Alle exportieren
|
||||||
exportSelected: Gewählte exportieren
|
exportSelected: Gewählte exportieren
|
||||||
federateBlocks: Anderen Instanzen mitteilen, wenn ich jemanden blockiere
|
federateBlocks: Andere Instanzen mitteilen, wenn ich jemanden blockiere
|
||||||
selectMode: Auswählen
|
selectMode: Auswählen
|
||||||
selectAll: Alle auswählen
|
selectAll: Alle auswählen
|
||||||
renoteUnmute: Renotes zeigen
|
renoteUnmute: Renotes zeigen
|
||||||
|
|
|
@ -242,6 +242,7 @@ uploadFromUrl: "Upload from a URL"
|
||||||
uploadFromUrlDescription: "URL of the file you want to upload"
|
uploadFromUrlDescription: "URL of the file you want to upload"
|
||||||
uploadFromUrlRequested: "Upload requested"
|
uploadFromUrlRequested: "Upload requested"
|
||||||
uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
||||||
|
explore: "Explore"
|
||||||
messageRead: "Read"
|
messageRead: "Read"
|
||||||
noMoreHistory: "There is no further history"
|
noMoreHistory: "There is no further history"
|
||||||
startMessaging: "Start a new chat"
|
startMessaging: "Start a new chat"
|
||||||
|
@ -323,6 +324,9 @@ inMb: "In megabytes"
|
||||||
iconUrl: "Icon URL"
|
iconUrl: "Icon URL"
|
||||||
bannerUrl: "Banner image URL"
|
bannerUrl: "Banner image URL"
|
||||||
backgroundImageUrl: "Background image URL"
|
backgroundImageUrl: "Background image URL"
|
||||||
|
pinnedUsers: "Pinned users"
|
||||||
|
pinnedUsersDescription: "List usernames separated by line breaks to be pinned in the\
|
||||||
|
\ \"Explore\" tab."
|
||||||
hcaptchaSiteKey: "Site key"
|
hcaptchaSiteKey: "Site key"
|
||||||
hcaptchaSecretKey: "Secret key"
|
hcaptchaSecretKey: "Secret key"
|
||||||
recaptchaSiteKey: "Site key"
|
recaptchaSiteKey: "Site key"
|
||||||
|
@ -349,6 +353,11 @@ silence: "Silence"
|
||||||
silenceConfirm: "Are you sure that you want to silence this user?"
|
silenceConfirm: "Are you sure that you want to silence this user?"
|
||||||
unsilence: "Undo silencing"
|
unsilence: "Undo silencing"
|
||||||
unsilenceConfirm: "Are you sure that you want to undo the silencing of this user?"
|
unsilenceConfirm: "Are you sure that you want to undo the silencing of this user?"
|
||||||
|
popularUsers: "Popular users"
|
||||||
|
recentlyUpdatedUsers: "Recently active users"
|
||||||
|
recentlyRegisteredUsers: "Newly joined users"
|
||||||
|
recentlyDiscoveredUsers: "Newly discovered users"
|
||||||
|
popularTags: "Popular tags"
|
||||||
userList: "Lists"
|
userList: "Lists"
|
||||||
aboutMisskey: "About FoundKey"
|
aboutMisskey: "About FoundKey"
|
||||||
administrator: "Administrator"
|
administrator: "Administrator"
|
||||||
|
@ -389,6 +398,7 @@ messagingWithGroup: "Group chat"
|
||||||
title: "Title"
|
title: "Title"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Enable"
|
enable: "Enable"
|
||||||
|
next: "Next"
|
||||||
retype: "Enter again"
|
retype: "Enter again"
|
||||||
noteOf: "Note by {user}"
|
noteOf: "Note by {user}"
|
||||||
inviteToGroup: "Invite to group"
|
inviteToGroup: "Invite to group"
|
||||||
|
@ -494,8 +504,6 @@ output: "Output"
|
||||||
updateRemoteUser: "Update remote user information"
|
updateRemoteUser: "Update remote user information"
|
||||||
deleteAllFiles: "Delete all files"
|
deleteAllFiles: "Delete all files"
|
||||||
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
|
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
|
||||||
deleteAllNotes: "Delete all notes"
|
|
||||||
deleteAllNotesConfirm: "Are you sure that you want to delete all visible notes of this clip?"
|
|
||||||
removeAllFollowing: "Unfollow all followed users"
|
removeAllFollowing: "Unfollow all followed users"
|
||||||
removeAllFollowingDescription: "Executing this unfollows all accounts from {host}.\
|
removeAllFollowingDescription: "Executing this unfollows all accounts from {host}.\
|
||||||
\ Please run this if the instance e.g. no longer exists."
|
\ Please run this if the instance e.g. no longer exists."
|
||||||
|
@ -586,6 +594,7 @@ abuseReports: "Reports"
|
||||||
reportAbuse: "Report"
|
reportAbuse: "Report"
|
||||||
reportAbuseOf: "Report {name}"
|
reportAbuseOf: "Report {name}"
|
||||||
fillAbuseReportDescription: "Please fill in details regarding this report."
|
fillAbuseReportDescription: "Please fill in details regarding this report."
|
||||||
|
abuseReported: "Your report has been sent. Thank you very much."
|
||||||
reporter: "Reporter"
|
reporter: "Reporter"
|
||||||
reporteeOrigin: "Reportee Origin"
|
reporteeOrigin: "Reportee Origin"
|
||||||
reporterOrigin: "Reporter Origin"
|
reporterOrigin: "Reporter Origin"
|
||||||
|
@ -636,6 +645,7 @@ disableShowingAnimatedImages: "Don't play animated images"
|
||||||
verificationEmailSent: "A verification email has been sent. Please follow the included\
|
verificationEmailSent: "A verification email has been sent. Please follow the included\
|
||||||
\ link to complete verification."
|
\ link to complete verification."
|
||||||
emailVerified: "Email has been verified"
|
emailVerified: "Email has been verified"
|
||||||
|
noteFavoritesCount: "Number of favorite notes"
|
||||||
pageLikesCount: "Number of liked Pages"
|
pageLikesCount: "Number of liked Pages"
|
||||||
pageLikedCount: "Number of received Page likes"
|
pageLikedCount: "Number of received Page likes"
|
||||||
contact: "Contact"
|
contact: "Contact"
|
||||||
|
@ -1020,6 +1030,39 @@ _time:
|
||||||
minute: "Minute(s)"
|
minute: "Minute(s)"
|
||||||
hour: "Hour(s)"
|
hour: "Hour(s)"
|
||||||
day: "Day(s)"
|
day: "Day(s)"
|
||||||
|
_tutorial:
|
||||||
|
title: "How to use FoundKey"
|
||||||
|
step1_1: "Welcome!"
|
||||||
|
step1_2: "This page is called the \"timeline\". It shows chronologically ordered\
|
||||||
|
\ \"notes\" of people who you \"follow\"."
|
||||||
|
step1_3: "Your timeline is currently empty, since you have not posted any notes\
|
||||||
|
\ or followed anyone yet."
|
||||||
|
step2_1: "Let's finish setting up your profile before writing a note or following\
|
||||||
|
\ anyone."
|
||||||
|
step2_2: "Providing some information about who you are will make it easier for others\
|
||||||
|
\ to tell if they want to see your notes or follow you."
|
||||||
|
step3_1: "Finished setting up your profile?"
|
||||||
|
step3_2: "Then let's try posting a note next. You can do so by pressing the button\
|
||||||
|
\ with a pencil icon on the screen."
|
||||||
|
step3_3: "Fill in the modal and press the button on the top right to post."
|
||||||
|
step3_4: "Have nothing to say? Try \"just setting up my msky\"!"
|
||||||
|
step4_1: "Finished posting your first note?"
|
||||||
|
step4_2: "Hurray! Now your first note should be displayed on your timeline."
|
||||||
|
step5_1: "Now, let's try making your timeline more lively by following other people."
|
||||||
|
step5_2: "{featured} will show you popular notes in this instance. {explore} will\
|
||||||
|
\ let you find popular users. Try finding people you'd like to follow there!"
|
||||||
|
step5_3: "To follow other users, click on their icon and press the \"Follow\" button\
|
||||||
|
\ on their profile."
|
||||||
|
step5_4: "If the other user has a lock icon next to their name, it may take some\
|
||||||
|
\ time for that user to manually approve your follow request."
|
||||||
|
step6_1: "You should be able to see other users' notes on your timeline now."
|
||||||
|
step6_2: "You can also put \"reactions\" on other people's notes to quickly respond\
|
||||||
|
\ to them."
|
||||||
|
step6_3: "To attach a \"reaction\", press the \"+\" mark on another user's note\
|
||||||
|
\ and choose an emoji you'd like to react with."
|
||||||
|
step7_1: "Congratulations! You have now finished FoundKey's basic tutorial."
|
||||||
|
step7_2: "If you would like to learn more about FoundKey, try the {help} section."
|
||||||
|
step7_3: "Now then, good luck and have fun with FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "You have already registered a 2-factor authentication device."
|
alreadyRegistered: "You have already registered a 2-factor authentication device."
|
||||||
registerDevice: "Register a new device"
|
registerDevice: "Register a new device"
|
||||||
|
@ -1205,7 +1248,6 @@ _timelines:
|
||||||
local: "Local"
|
local: "Local"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
shuffled: "Shuffled"
|
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Create a new Page"
|
newPage: "Create a new Page"
|
||||||
editPage: "Edit this Page"
|
editPage: "Edit this Page"
|
||||||
|
@ -1263,7 +1305,6 @@ _notification:
|
||||||
reaction: "Reactions"
|
reaction: "Reactions"
|
||||||
pollVote: "Votes on polls"
|
pollVote: "Votes on polls"
|
||||||
pollEnded: "Polls ending"
|
pollEnded: "Polls ending"
|
||||||
update: "Watched Note was updated"
|
|
||||||
receiveFollowRequest: "Received follow requests"
|
receiveFollowRequest: "Received follow requests"
|
||||||
followRequestAccepted: "Accepted follow requests"
|
followRequestAccepted: "Accepted follow requests"
|
||||||
groupInvited: "Group invitations"
|
groupInvited: "Group invitations"
|
||||||
|
|
|
@ -33,6 +33,9 @@ signup: "Registrarse"
|
||||||
save: "Guardar"
|
save: "Guardar"
|
||||||
users: "Usuarios"
|
users: "Usuarios"
|
||||||
addUser: "Agregar usuario"
|
addUser: "Agregar usuario"
|
||||||
|
favorite: "Favorito"
|
||||||
|
favorites: "Favoritos"
|
||||||
|
unfavorite: "Quitar de favoritos"
|
||||||
pin: "Fijar"
|
pin: "Fijar"
|
||||||
unpin: "Desfijar"
|
unpin: "Desfijar"
|
||||||
copyContent: "Copiar contenido"
|
copyContent: "Copiar contenido"
|
||||||
|
@ -238,6 +241,7 @@ uploadFromUrl: "Subir desde una URL"
|
||||||
uploadFromUrlDescription: "URL del fichero que quieres subir"
|
uploadFromUrlDescription: "URL del fichero que quieres subir"
|
||||||
uploadFromUrlRequested: "Subida solicitada"
|
uploadFromUrlRequested: "Subida solicitada"
|
||||||
uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo."
|
uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo."
|
||||||
|
explore: "Explorar"
|
||||||
messageRead: "Ya leído"
|
messageRead: "Ya leído"
|
||||||
noMoreHistory: "El historial se ha acabado"
|
noMoreHistory: "El historial se ha acabado"
|
||||||
startMessaging: "Iniciar chat"
|
startMessaging: "Iniciar chat"
|
||||||
|
@ -318,6 +322,9 @@ inMb: "En megabytes"
|
||||||
iconUrl: "URL de la imagen del avatar"
|
iconUrl: "URL de la imagen del avatar"
|
||||||
bannerUrl: "URL de la imagen del banner"
|
bannerUrl: "URL de la imagen del banner"
|
||||||
backgroundImageUrl: "URL de la imagen de fondo"
|
backgroundImageUrl: "URL de la imagen de fondo"
|
||||||
|
pinnedUsers: "Usuarios fijados"
|
||||||
|
pinnedUsersDescription: "Describir los usuarios que quiere fijar en la página \"Descubrir\"\
|
||||||
|
\ separados por una linea nueva"
|
||||||
hcaptchaSiteKey: "Clave del sitio"
|
hcaptchaSiteKey: "Clave del sitio"
|
||||||
hcaptchaSecretKey: "Clave secreta"
|
hcaptchaSecretKey: "Clave secreta"
|
||||||
recaptchaSiteKey: "Clave del sitio"
|
recaptchaSiteKey: "Clave del sitio"
|
||||||
|
@ -342,6 +349,11 @@ silence: "Silenciar"
|
||||||
silenceConfirm: "¿Desea silenciar al usuario?"
|
silenceConfirm: "¿Desea silenciar al usuario?"
|
||||||
unsilence: "Dejar de silenciar"
|
unsilence: "Dejar de silenciar"
|
||||||
unsilenceConfirm: "¿Desea dejar de silenciar al usuario?"
|
unsilenceConfirm: "¿Desea dejar de silenciar al usuario?"
|
||||||
|
popularUsers: "Usuarios populares"
|
||||||
|
recentlyUpdatedUsers: "Usuarios activos recientemente"
|
||||||
|
recentlyRegisteredUsers: "Usuarios registrados recientemente"
|
||||||
|
recentlyDiscoveredUsers: "Usuarios descubiertos recientemente"
|
||||||
|
popularTags: "Etiquetas populares"
|
||||||
userList: "Lista"
|
userList: "Lista"
|
||||||
aboutMisskey: "Sobre FoundKey"
|
aboutMisskey: "Sobre FoundKey"
|
||||||
administrator: "Administrador"
|
administrator: "Administrador"
|
||||||
|
@ -382,6 +394,7 @@ messagingWithGroup: "Chatear en grupo"
|
||||||
title: "Título"
|
title: "Título"
|
||||||
text: "Texto"
|
text: "Texto"
|
||||||
enable: "Activar"
|
enable: "Activar"
|
||||||
|
next: "Siguiente"
|
||||||
retype: "Intentar de nuevo"
|
retype: "Intentar de nuevo"
|
||||||
noteOf: "Notas de {user}"
|
noteOf: "Notas de {user}"
|
||||||
inviteToGroup: "Invitar al grupo"
|
inviteToGroup: "Invitar al grupo"
|
||||||
|
@ -573,6 +586,7 @@ abuseReports: "Reportes"
|
||||||
reportAbuse: "Reportar"
|
reportAbuse: "Reportar"
|
||||||
reportAbuseOf: "Reportar a {name}"
|
reportAbuseOf: "Reportar a {name}"
|
||||||
fillAbuseReportDescription: "Ingrese los detalles del reporte."
|
fillAbuseReportDescription: "Ingrese los detalles del reporte."
|
||||||
|
abuseReported: "Se ha enviado el reporte. Muchas gracias."
|
||||||
reporteeOrigin: "Informar a"
|
reporteeOrigin: "Informar a"
|
||||||
reporterOrigin: "Origen del informe"
|
reporterOrigin: "Origen del informe"
|
||||||
forwardReport: "Transferir un informe a una instancia remota"
|
forwardReport: "Transferir un informe a una instancia remota"
|
||||||
|
@ -624,6 +638,7 @@ verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación
|
||||||
\ favor, acceda al enlace proporcionado en el correo electrónico para completar\
|
\ favor, acceda al enlace proporcionado en el correo electrónico para completar\
|
||||||
\ la configuración."
|
\ la configuración."
|
||||||
emailVerified: "Su dirección de correo electrónico ha sido verificada."
|
emailVerified: "Su dirección de correo electrónico ha sido verificada."
|
||||||
|
noteFavoritesCount: "Número de notas favoritas"
|
||||||
pageLikesCount: "Número de favoritos en la página"
|
pageLikesCount: "Número de favoritos en la página"
|
||||||
pageLikedCount: "Número de favoritos de su página"
|
pageLikedCount: "Número de favoritos de su página"
|
||||||
contact: "Contacto"
|
contact: "Contacto"
|
||||||
|
@ -925,6 +940,41 @@ _time:
|
||||||
minute: "Minutos"
|
minute: "Minutos"
|
||||||
hour: "Horas"
|
hour: "Horas"
|
||||||
day: "Días"
|
day: "Días"
|
||||||
|
_tutorial:
|
||||||
|
title: "Cómo usar FoundKey"
|
||||||
|
step1_1: "Bienvenido"
|
||||||
|
step1_2: "Esta imagen se llama \"Linea de tiempo\" y muestra en orden cronológico\
|
||||||
|
\ las \"notas\" tuyas y de la gente que \"sigues\""
|
||||||
|
step1_3: "Si no estás escribiendo ninguna nota y no estás siguiendo a nadie, es\
|
||||||
|
\ esperable que no se muestre nada en la linea de tiempo"
|
||||||
|
step2_1: "Antes de crear notas y seguir a alguien, primero vamos a crear tu perfil"
|
||||||
|
step2_2: "Si provees información sobre quien eres, será más fácil para que otros\
|
||||||
|
\ usuarios te sigan"
|
||||||
|
step3_1: "¿Has podido crear tu perfil sin problemas?"
|
||||||
|
step3_2: "Con esto, prueba hacer una nota. Aprieta el botón con forma de lápiz que\
|
||||||
|
\ está arriba de la imagen y abre el formulario."
|
||||||
|
step3_3: "Si has escrito el contenido, aprieta el botón que está arriba a la derecha\
|
||||||
|
\ del formulario para postear."
|
||||||
|
step3_4: "¿No se te ocurre un contenido? Prueba con decir \"Empecé a usar FoundKey\""
|
||||||
|
step4_1: "¿Has posteado?"
|
||||||
|
step4_2: "Si tu nota puede verse en la linea de tiempo, fue todo un éxito."
|
||||||
|
step5_1: "Luego, ponte a seguir a otra gente y haz que tu linea de tiempo esté más\
|
||||||
|
\ animada."
|
||||||
|
step5_2: "Puedes ver las notas destacadas en {featured} y desde allí seguir a usuarios\
|
||||||
|
\ que te importan. También puedes buscar usuario destacados en {explore}."
|
||||||
|
step5_3: "Para seguir a un usuario, haz click en su avatar para ver su página de\
|
||||||
|
\ usuario y allí apretar el botón \"seguir\""
|
||||||
|
step5_4: "De esa manera, puede pasar un tiempo hasta que el usuario apruebe al seguidor."
|
||||||
|
step6_1: "Si puedes ver en la linea de tiempo las notas de otros usuarios, fue todo\
|
||||||
|
\ un éxito."
|
||||||
|
step6_2: "En las notas de otros usuarios puedes añadir una \"reacción\", para poder\
|
||||||
|
\ responder rápidamente."
|
||||||
|
step6_3: "Para añadir una reacción, haz click en el botón \"+\" de la nota y elige\
|
||||||
|
\ la reacción que prefieras."
|
||||||
|
step7_1: "Así terminó la explicación del funcionamiento básico de FoundKey. Eso\
|
||||||
|
\ fue todo."
|
||||||
|
step7_2: "Si quieres conocer más sobre FoundKey, prueba con la sección {help}."
|
||||||
|
step7_3: "Así, disfruta de FoundKey \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Ya has completado la configuración."
|
alreadyRegistered: "Ya has completado la configuración."
|
||||||
registerDevice: "Registrar dispositivo"
|
registerDevice: "Registrar dispositivo"
|
||||||
|
|
|
@ -34,6 +34,9 @@ signup: "S’inscrire"
|
||||||
save: "Enregistrer"
|
save: "Enregistrer"
|
||||||
users: "Utilisateur·rice·s"
|
users: "Utilisateur·rice·s"
|
||||||
addUser: "Ajouter un·e utilisateur·rice"
|
addUser: "Ajouter un·e utilisateur·rice"
|
||||||
|
favorite: "Ajouter aux favoris"
|
||||||
|
favorites: "Favoris"
|
||||||
|
unfavorite: "Retirer des favoris"
|
||||||
pin: "Épingler sur le profil"
|
pin: "Épingler sur le profil"
|
||||||
unpin: "Désépingler"
|
unpin: "Désépingler"
|
||||||
copyContent: "Copier le contenu"
|
copyContent: "Copier le contenu"
|
||||||
|
@ -238,6 +241,7 @@ uploadFromUrlDescription: "URL du fichier que vous souhaitez téléverser"
|
||||||
uploadFromUrlRequested: "Téléversement demandé"
|
uploadFromUrlRequested: "Téléversement demandé"
|
||||||
uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain\
|
uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain\
|
||||||
\ temps."
|
\ temps."
|
||||||
|
explore: "Découvrir"
|
||||||
messageRead: "Lu"
|
messageRead: "Lu"
|
||||||
noMoreHistory: "Il n’y a plus d’historique"
|
noMoreHistory: "Il n’y a plus d’historique"
|
||||||
startMessaging: "Commencer à discuter"
|
startMessaging: "Commencer à discuter"
|
||||||
|
@ -319,6 +323,9 @@ inMb: "en mégaoctets"
|
||||||
iconUrl: "URL de l'icône"
|
iconUrl: "URL de l'icône"
|
||||||
bannerUrl: "URL de l’image de la bannière"
|
bannerUrl: "URL de l’image de la bannière"
|
||||||
backgroundImageUrl: "URL de l'image d'arrière-plan"
|
backgroundImageUrl: "URL de l'image d'arrière-plan"
|
||||||
|
pinnedUsers: "Utilisateur·rice épinglé·e"
|
||||||
|
pinnedUsersDescription: "Listez les utilisateur·rice·s que vous souhaitez voir épinglé·e·s\
|
||||||
|
\ sur la page \"Découvrir\", un·e par ligne."
|
||||||
hcaptchaSiteKey: "Clé du site"
|
hcaptchaSiteKey: "Clé du site"
|
||||||
hcaptchaSecretKey: "Clé secrète"
|
hcaptchaSecretKey: "Clé secrète"
|
||||||
recaptchaSiteKey: "Clé du site"
|
recaptchaSiteKey: "Clé du site"
|
||||||
|
@ -345,6 +352,11 @@ silenceConfirm: "Êtes-vous sûr·e de vouloir mettre l’utilisateur·rice en s
|
||||||
unsilence: "Annuler la sourdine"
|
unsilence: "Annuler la sourdine"
|
||||||
unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te\
|
unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te\
|
||||||
\ utilisateur·rice ?"
|
\ utilisateur·rice ?"
|
||||||
|
popularUsers: "Utilisateur·rice·s populaires"
|
||||||
|
recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment"
|
||||||
|
recentlyRegisteredUsers: "Utilisateur·rice·s récemment inscrit·e·s"
|
||||||
|
recentlyDiscoveredUsers: "Utilisateur·rice·s récemment découvert·e·s"
|
||||||
|
popularTags: "Mots-clés populaires"
|
||||||
userList: "Listes"
|
userList: "Listes"
|
||||||
aboutMisskey: "À propos de FoundKey"
|
aboutMisskey: "À propos de FoundKey"
|
||||||
administrator: "Administrateur"
|
administrator: "Administrateur"
|
||||||
|
@ -385,6 +397,7 @@ messagingWithGroup: "Discuter avec un groupe"
|
||||||
title: "Titre"
|
title: "Titre"
|
||||||
text: "Texte"
|
text: "Texte"
|
||||||
enable: "Activer"
|
enable: "Activer"
|
||||||
|
next: "Suivant"
|
||||||
retype: "Confirmation"
|
retype: "Confirmation"
|
||||||
noteOf: "Notes de {user}"
|
noteOf: "Notes de {user}"
|
||||||
inviteToGroup: "Inviter dans un groupe"
|
inviteToGroup: "Inviter dans un groupe"
|
||||||
|
@ -585,6 +598,7 @@ abuseReports: "Signalements"
|
||||||
reportAbuse: "Signaler"
|
reportAbuse: "Signaler"
|
||||||
reportAbuseOf: "Signaler {name}"
|
reportAbuseOf: "Signaler {name}"
|
||||||
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement."
|
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement."
|
||||||
|
abuseReported: "Le rapport est envoyé. Merci."
|
||||||
reporter: "Signalé par"
|
reporter: "Signalé par"
|
||||||
reporteeOrigin: "Origine du signalement"
|
reporteeOrigin: "Origine du signalement"
|
||||||
reporterOrigin: "Signalé par"
|
reporterOrigin: "Signalé par"
|
||||||
|
@ -633,6 +647,7 @@ disableShowingAnimatedImages: "Désactiver l'animation des images"
|
||||||
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\
|
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\
|
||||||
\ lien pour compléter la vérification."
|
\ lien pour compléter la vérification."
|
||||||
emailVerified: "Votre adresse e-mail a été vérifiée"
|
emailVerified: "Votre adresse e-mail a été vérifiée"
|
||||||
|
noteFavoritesCount: "Nombre de notes dans les favoris"
|
||||||
pageLikesCount: "Nombre de pages aimées"
|
pageLikesCount: "Nombre de pages aimées"
|
||||||
pageLikedCount: "Nombre de vos pages aimées"
|
pageLikedCount: "Nombre de vos pages aimées"
|
||||||
contact: "Contact"
|
contact: "Contact"
|
||||||
|
@ -982,6 +997,46 @@ _time:
|
||||||
minute: "min"
|
minute: "min"
|
||||||
hour: "h"
|
hour: "h"
|
||||||
day: "j"
|
day: "j"
|
||||||
|
_tutorial:
|
||||||
|
title: "Comment utiliser FoundKey"
|
||||||
|
step1_1: "Bienvenue !"
|
||||||
|
step1_2: "Cette page est appelée « un fil ». Elle affiche les « notes » des personnes\
|
||||||
|
\ auxquelles vous êtes abonné dans un ordre chronologique."
|
||||||
|
step1_3: "Votre fil est actuellement vide vu que vous ne suivez aucun compte et\
|
||||||
|
\ que vous n’avez publié aucune note, pour l’instant."
|
||||||
|
step2_1: "Procédons d’abord à la préparation de votre profil avant d’écrire une\
|
||||||
|
\ note et/ou de vous abonner à un compte."
|
||||||
|
step2_2: "En fournissant quelques informations sur vous, il sera plus facile pour\
|
||||||
|
\ les autres de s’abonner à votre compte."
|
||||||
|
step3_1: "Vous avez fini de créer votre profil ?"
|
||||||
|
step3_2: "L’étape suivante consiste à créer une note. Vous pouvez commencer en cliquant\
|
||||||
|
\ sur l’icône crayon sur l’écran."
|
||||||
|
step3_3: "Remplissez le cadran et cliquez sur le bouton en haut à droite pour envoyer."
|
||||||
|
step3_4: "Vous n’avez rien à dire ? Essayez d’écrire « J’ai commencé à utiliser\
|
||||||
|
\ FoundKey » !"
|
||||||
|
step4_1: "Avez-vous publié votre première note ?"
|
||||||
|
step4_2: "Youpi ! Celle-ci est maintenant affichée sur votre fil d’actualité."
|
||||||
|
step5_1: "Maintenant, essayons de nous abonner à d’autres personnes afin de rendre\
|
||||||
|
\ votre fil plus vivant."
|
||||||
|
step5_2: "La page {featured} affiche les notes en tendance sur la présente instance\
|
||||||
|
\ et {explore} vous permet de trouver des utilisateur·rice·s en tendance. Essayez\
|
||||||
|
\ de vous abonner aux gens que vous aimez !"
|
||||||
|
step5_3: "Pour pouvoir suivre d’autres utilisateur·rice, cliquez sur leur avatar\
|
||||||
|
\ afin d’afficher la page du profil utilisateur ensuite appuyez sur le bouton\
|
||||||
|
\ « S’abonner »."
|
||||||
|
step5_4: "Si l’autre utilisateur possède une icône sous forme d’un cadenas à côté\
|
||||||
|
\ de son nom, il devra accepter votre demande d’abonnement manuellement."
|
||||||
|
step6_1: "Maintenant, vous êtes en mesure de voir s’afficher les notes des autres\
|
||||||
|
\ utilisateur·rice·s sur votre propre fil."
|
||||||
|
step6_2: "Vous avez également la possibilité d’intéragir rapidement avec les notes\
|
||||||
|
\ des autres utilisateur·rice·s en ajoutant des « réactions »."
|
||||||
|
step6_3: "Pour ajouter une réaction à une note, cliquez sur le signe « + » de celle-ci\
|
||||||
|
\ et sélectionnez l’émoji souhaité."
|
||||||
|
step7_1: "Félicitations ! Vous avez atteint la fin du tutoriel de base pour l’utilisation\
|
||||||
|
\ de FoundKey."
|
||||||
|
step7_2: "Si vous désirez en savoir plus sur FoundKey, jetez un œil sur la section\
|
||||||
|
\ {help}."
|
||||||
|
step7_3: "Bon courage et amusez-vous bien sur FoundKey ! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Configuration déjà achevée."
|
alreadyRegistered: "Configuration déjà achevée."
|
||||||
registerDevice: "Ajouter un nouvel appareil"
|
registerDevice: "Ajouter un nouvel appareil"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Daftar"
|
||||||
save: "Simpan"
|
save: "Simpan"
|
||||||
users: "Pengguna"
|
users: "Pengguna"
|
||||||
addUser: "Tambah pengguna"
|
addUser: "Tambah pengguna"
|
||||||
|
favorite: "Favorit"
|
||||||
|
favorites: "Favorit"
|
||||||
|
unfavorite: "Hapus favorit"
|
||||||
pin: "Sematkan ke profil"
|
pin: "Sematkan ke profil"
|
||||||
unpin: "Lepas sematan dari profil"
|
unpin: "Lepas sematan dari profil"
|
||||||
copyContent: "Salin konten"
|
copyContent: "Salin konten"
|
||||||
|
@ -235,6 +238,7 @@ uploadFromUrl: "Unggah dari URL"
|
||||||
uploadFromUrlDescription: "URL berkas yang ingin kamu unggah"
|
uploadFromUrlDescription: "URL berkas yang ingin kamu unggah"
|
||||||
uploadFromUrlRequested: "Pengunggahan telah diminta"
|
uploadFromUrlRequested: "Pengunggahan telah diminta"
|
||||||
uploadFromUrlMayTakeTime: "Mungkin diperlukan waktu hingga unggahan selesai."
|
uploadFromUrlMayTakeTime: "Mungkin diperlukan waktu hingga unggahan selesai."
|
||||||
|
explore: "Jelajahi"
|
||||||
messageRead: "Telah dibaca"
|
messageRead: "Telah dibaca"
|
||||||
noMoreHistory: "Tidak ada sejarah lagi"
|
noMoreHistory: "Tidak ada sejarah lagi"
|
||||||
startMessaging: "Mulai mengirim pesan"
|
startMessaging: "Mulai mengirim pesan"
|
||||||
|
@ -316,6 +320,9 @@ inMb: "dalam Megabytes"
|
||||||
iconUrl: "URL Gambar ikon"
|
iconUrl: "URL Gambar ikon"
|
||||||
bannerUrl: "URL Banner"
|
bannerUrl: "URL Banner"
|
||||||
backgroundImageUrl: "URL Gambar latar"
|
backgroundImageUrl: "URL Gambar latar"
|
||||||
|
pinnedUsers: "Pengguna yang disematkan"
|
||||||
|
pinnedUsersDescription: "Tuliskan satu nama pengguna dalam satu baris. Pengguna yang\
|
||||||
|
\ dituliskan disini akan disematkan dalam bilah \"Jelajahi\"."
|
||||||
hcaptchaSiteKey: "Site Key"
|
hcaptchaSiteKey: "Site Key"
|
||||||
hcaptchaSecretKey: "Secret Key"
|
hcaptchaSecretKey: "Secret Key"
|
||||||
recaptchaSiteKey: "Site key"
|
recaptchaSiteKey: "Site key"
|
||||||
|
@ -340,6 +347,11 @@ silence: "Bungkam"
|
||||||
silenceConfirm: "Apakah kamu yakin ingin membungkam pengguna ini?"
|
silenceConfirm: "Apakah kamu yakin ingin membungkam pengguna ini?"
|
||||||
unsilence: "Hapus bungkam"
|
unsilence: "Hapus bungkam"
|
||||||
unsilenceConfirm: "Apakah kamu ingin untuk batal membungkam pengguna ini?"
|
unsilenceConfirm: "Apakah kamu ingin untuk batal membungkam pengguna ini?"
|
||||||
|
popularUsers: "Pengguna populer"
|
||||||
|
recentlyUpdatedUsers: "Pengguna dengan aktivitas terkini"
|
||||||
|
recentlyRegisteredUsers: "Pengguna baru saja bergabung"
|
||||||
|
recentlyDiscoveredUsers: "Pengguna baru saja dilihat"
|
||||||
|
popularTags: "Tag populer"
|
||||||
userList: "Daftar"
|
userList: "Daftar"
|
||||||
aboutMisskey: "Tentang FoundKey"
|
aboutMisskey: "Tentang FoundKey"
|
||||||
administrator: "Admin"
|
administrator: "Admin"
|
||||||
|
@ -380,6 +392,7 @@ messagingWithGroup: "Obrolan di dalam grup"
|
||||||
title: "Judul"
|
title: "Judul"
|
||||||
text: "Teks"
|
text: "Teks"
|
||||||
enable: "Aktifkan"
|
enable: "Aktifkan"
|
||||||
|
next: "Selanjutnya"
|
||||||
retype: "Masukkan ulang"
|
retype: "Masukkan ulang"
|
||||||
noteOf: "Catatan milik {user}"
|
noteOf: "Catatan milik {user}"
|
||||||
inviteToGroup: "Undang ke grup"
|
inviteToGroup: "Undang ke grup"
|
||||||
|
@ -575,6 +588,7 @@ abuseReports: "Laporkan"
|
||||||
reportAbuse: "Laporkan"
|
reportAbuse: "Laporkan"
|
||||||
reportAbuseOf: "Laporkan {name}"
|
reportAbuseOf: "Laporkan {name}"
|
||||||
fillAbuseReportDescription: "Mohon isi rincian laporan."
|
fillAbuseReportDescription: "Mohon isi rincian laporan."
|
||||||
|
abuseReported: "Laporan kamu telah dikirimkan. Terima kasih."
|
||||||
reporter: "Pelapor"
|
reporter: "Pelapor"
|
||||||
reporteeOrigin: "Yang dilaporkan"
|
reporteeOrigin: "Yang dilaporkan"
|
||||||
reporterOrigin: "Pelapor"
|
reporterOrigin: "Pelapor"
|
||||||
|
@ -625,6 +639,7 @@ disableShowingAnimatedImages: "Jangan mainkan gambar bergerak"
|
||||||
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\
|
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\
|
||||||
\ telah disertakan untuk menyelesaikan verifikasi."
|
\ telah disertakan untuk menyelesaikan verifikasi."
|
||||||
emailVerified: "Surel telah diverifikasi"
|
emailVerified: "Surel telah diverifikasi"
|
||||||
|
noteFavoritesCount: "Jumlah catatan yang difavoritkan"
|
||||||
pageLikesCount: "Jumlah suka yang diterima Halaman"
|
pageLikesCount: "Jumlah suka yang diterima Halaman"
|
||||||
pageLikedCount: "Jumlah Halaman yang disukai"
|
pageLikedCount: "Jumlah Halaman yang disukai"
|
||||||
contact: "Kontak"
|
contact: "Kontak"
|
||||||
|
@ -978,6 +993,44 @@ _time:
|
||||||
minute: "menit"
|
minute: "menit"
|
||||||
hour: "jam"
|
hour: "jam"
|
||||||
day: "hari"
|
day: "hari"
|
||||||
|
_tutorial:
|
||||||
|
title: "Cara menggunakan FoundKey"
|
||||||
|
step1_1: "Selamat datang!"
|
||||||
|
step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\"\
|
||||||
|
\ yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"."
|
||||||
|
step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti\
|
||||||
|
\ siapapun."
|
||||||
|
step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti\
|
||||||
|
\ seseorang."
|
||||||
|
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain\
|
||||||
|
\ mudah untuk mengikutimu kembali."
|
||||||
|
step3_1: "Selesai menyetel profil kamu?"
|
||||||
|
step3_2: "Langkah selanjutnya adalah membuat catatan. Kamu bisa lakukan ini dengan\
|
||||||
|
\ mengklik ikon pensil pada layar kamu."
|
||||||
|
step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat\
|
||||||
|
\ catatan kamu."
|
||||||
|
step3_4: "Bingung tidak berpikiran untuk mengatakan sesuatu? Coba saja \"baru aja\
|
||||||
|
\ ikutan bikin akun misskey punyaku\"!"
|
||||||
|
step4_1: "Selesai mencatat catatan pertamamu?"
|
||||||
|
step4_2: "Horee! Sekarang catatan pertamamu sudah ditampilkan di linimasa milikmu."
|
||||||
|
step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti\
|
||||||
|
\ orang lain."
|
||||||
|
step5_2: "{featured} akan memperlihatkan catatan yang sedang tren saat ini untuk\
|
||||||
|
\ kamu. {explore} akan membantumu untuk mencari pengguna yang sedang tren juga\
|
||||||
|
\ saat ini. Coba ikuti seseorang yang kamu suka!"
|
||||||
|
step5_3: "Untuk mengikuti pengguna lain, klik pada ikon mereka dan tekan tombol\
|
||||||
|
\ follow pada profil mereka."
|
||||||
|
step5_4: "Jika pengguna lain memiliki ikon gembok di sebelah nama mereka, maka pengguna\
|
||||||
|
\ rersebut harus menyetujui permintaan mengikuti dari kamu secara manual."
|
||||||
|
step6_1: "Sekarang kamu dapat melihat catatan pengguna lain pada linimasamu."
|
||||||
|
step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon\
|
||||||
|
\ dengan cepat."
|
||||||
|
step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain\
|
||||||
|
\ dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka."
|
||||||
|
step7_1: "Yay, Selamat! Kamu sudah menyelesaikan tutorial dasar FoundKey."
|
||||||
|
step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang FoundKey, cobalah berkunjung\
|
||||||
|
\ ke bagian {help}."
|
||||||
|
step7_3: "Semoga berhasil dan bersenang-senanglah! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
|
alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
|
||||||
registerDevice: "Daftarkan perangkat baru"
|
registerDevice: "Daftarkan perangkat baru"
|
||||||
|
|
|
@ -27,6 +27,7 @@ const languages = [
|
||||||
'id-ID',
|
'id-ID',
|
||||||
'it-IT',
|
'it-IT',
|
||||||
'ja-JP',
|
'ja-JP',
|
||||||
|
'ja-KS',
|
||||||
'kab-KAB',
|
'kab-KAB',
|
||||||
'kn-IN',
|
'kn-IN',
|
||||||
'ko-KR',
|
'ko-KR',
|
||||||
|
|
|
@ -33,6 +33,9 @@ signup: "Iscriviti"
|
||||||
save: "Salva"
|
save: "Salva"
|
||||||
users: "Utente"
|
users: "Utente"
|
||||||
addUser: "Aggiungi utente"
|
addUser: "Aggiungi utente"
|
||||||
|
favorite: "Preferiti"
|
||||||
|
favorites: "Preferiti"
|
||||||
|
unfavorite: "Rimuovi nota dai preferiti"
|
||||||
pin: "Fissa sul profilo"
|
pin: "Fissa sul profilo"
|
||||||
unpin: "Non fissare sul profilo"
|
unpin: "Non fissare sul profilo"
|
||||||
copyContent: "Copia il contenuto"
|
copyContent: "Copia il contenuto"
|
||||||
|
@ -229,6 +232,7 @@ uploadFromUrl: "Incolla URL immagine"
|
||||||
uploadFromUrlDescription: "URL del file che vuoi caricare"
|
uploadFromUrlDescription: "URL del file che vuoi caricare"
|
||||||
uploadFromUrlRequested: "Caricamento richiesto"
|
uploadFromUrlRequested: "Caricamento richiesto"
|
||||||
uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo."
|
uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo."
|
||||||
|
explore: "Esplora"
|
||||||
messageRead: "Visualizzato"
|
messageRead: "Visualizzato"
|
||||||
noMoreHistory: "Non c'è più cronologia da visualizzare"
|
noMoreHistory: "Non c'è più cronologia da visualizzare"
|
||||||
startMessaging: "Nuovo messaggio"
|
startMessaging: "Nuovo messaggio"
|
||||||
|
@ -310,6 +314,9 @@ inMb: "in Megabytes"
|
||||||
iconUrl: "URL di icona (favicon, ecc.)"
|
iconUrl: "URL di icona (favicon, ecc.)"
|
||||||
bannerUrl: "URL dell'immagine d'intestazione"
|
bannerUrl: "URL dell'immagine d'intestazione"
|
||||||
backgroundImageUrl: "URL dello sfondo"
|
backgroundImageUrl: "URL dello sfondo"
|
||||||
|
pinnedUsers: "Utenti in evidenza"
|
||||||
|
pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina\
|
||||||
|
\ \"Esplora\", un@ per riga."
|
||||||
hcaptchaSiteKey: "Chiave del sito"
|
hcaptchaSiteKey: "Chiave del sito"
|
||||||
hcaptchaSecretKey: "Chiave segreta"
|
hcaptchaSecretKey: "Chiave segreta"
|
||||||
recaptchaSiteKey: "Chiave del sito"
|
recaptchaSiteKey: "Chiave del sito"
|
||||||
|
@ -334,6 +341,11 @@ silence: "Silenzia"
|
||||||
silenceConfirm: "Vuoi davvero silenziare l'utente?"
|
silenceConfirm: "Vuoi davvero silenziare l'utente?"
|
||||||
unsilence: "Riattiva"
|
unsilence: "Riattiva"
|
||||||
unsilenceConfirm: "Vuoi davvero riattivare l'utente?"
|
unsilenceConfirm: "Vuoi davvero riattivare l'utente?"
|
||||||
|
popularUsers: "Utenti popolari"
|
||||||
|
recentlyUpdatedUsers: "Utenti attivi di recente"
|
||||||
|
recentlyRegisteredUsers: "Utenti registrati di recente"
|
||||||
|
recentlyDiscoveredUsers: "Utenti scoperti di recente"
|
||||||
|
popularTags: "Tag di tendenza"
|
||||||
userList: "Liste"
|
userList: "Liste"
|
||||||
aboutMisskey: "Informazioni di FoundKey"
|
aboutMisskey: "Informazioni di FoundKey"
|
||||||
administrator: "Amministratore"
|
administrator: "Amministratore"
|
||||||
|
@ -374,6 +386,7 @@ messagingWithGroup: "Chattare in gruppo"
|
||||||
title: "Titolo"
|
title: "Titolo"
|
||||||
text: "Testo"
|
text: "Testo"
|
||||||
enable: "Abilita"
|
enable: "Abilita"
|
||||||
|
next: "Avanti"
|
||||||
retype: "Conferma"
|
retype: "Conferma"
|
||||||
noteOf: "Note di {user}"
|
noteOf: "Note di {user}"
|
||||||
inviteToGroup: "Invitare al gruppo"
|
inviteToGroup: "Invitare al gruppo"
|
||||||
|
@ -564,6 +577,7 @@ abuseReports: "Segnalazioni"
|
||||||
reportAbuse: "Segnalazioni"
|
reportAbuse: "Segnalazioni"
|
||||||
reportAbuseOf: "Segnala {name}"
|
reportAbuseOf: "Segnala {name}"
|
||||||
fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione."
|
fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione."
|
||||||
|
abuseReported: "La segnalazione è stata inviata. Grazie."
|
||||||
reporter: "il corrispondente"
|
reporter: "il corrispondente"
|
||||||
reporteeOrigin: "Origine del segnalato"
|
reporteeOrigin: "Origine del segnalato"
|
||||||
reporterOrigin: "Origine del segnalatore"
|
reporterOrigin: "Origine del segnalatore"
|
||||||
|
@ -611,6 +625,7 @@ disableShowingAnimatedImages: "Disabilita le immagini animate"
|
||||||
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere\
|
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere\
|
||||||
\ al collegamento per compiere la verifica."
|
\ al collegamento per compiere la verifica."
|
||||||
emailVerified: "Il tuo indirizzo email è stato verificato"
|
emailVerified: "Il tuo indirizzo email è stato verificato"
|
||||||
|
noteFavoritesCount: "Conteggio note tra i preferiti"
|
||||||
pageLikesCount: "Numero di pagine che ti piacciono"
|
pageLikesCount: "Numero di pagine che ti piacciono"
|
||||||
pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\""
|
pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\""
|
||||||
contact: "Contatti"
|
contact: "Contatti"
|
||||||
|
@ -890,6 +905,45 @@ _time:
|
||||||
minute: "min"
|
minute: "min"
|
||||||
hour: "ore"
|
hour: "ore"
|
||||||
day: "giorni"
|
day: "giorni"
|
||||||
|
_tutorial:
|
||||||
|
title: "Come usare FoundKey"
|
||||||
|
step1_1: "Benvenuto/a!"
|
||||||
|
step1_2: "Questa pagina si chiama una \" Timeline \". Mostra in ordine cronologico\
|
||||||
|
\ le \" note \" delle persone che segui."
|
||||||
|
step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun account e non\
|
||||||
|
\ hai pubblicato alcuna nota ancora."
|
||||||
|
step2_1: "Prima di scrivere una nota o di seguire un account, imposta il tuo profilo!"
|
||||||
|
step2_2: "Aggiungere qualche informazione su di te aumenterà le tue possibilità\
|
||||||
|
\ di essere seguit@ da altre persone."
|
||||||
|
step3_1: "Hai finito di impostare il tuo profilo?"
|
||||||
|
step3_2: "Ora, puoi pubblicare una nota. Facciamo una prova! Premi il pulsante a\
|
||||||
|
\ forma di penna in cima allo schermo per aprire una finestra di dialogo."
|
||||||
|
step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella\
|
||||||
|
\ parte superiore destra della finestra di dialogo."
|
||||||
|
step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena\
|
||||||
|
\ cominciato a usare FoundKey\"?"
|
||||||
|
step4_1: "Hai pubblicato qualcosa?"
|
||||||
|
step4_2: "Se puoi visualizzare la tua nota sulla timeline, ce l'hai fatta!"
|
||||||
|
step5_1: "Adesso, cerca di seguire altre persone per vivacizzare la tua timeline."
|
||||||
|
step5_2: "La pagina {featured} mostra le note di tendenza su questa istanza, e magari\
|
||||||
|
\ ti aiuterà a trovare account che ti piacciono e che vorrai seguire. Oppure,\
|
||||||
|
\ potrai trovare utenti popolari usando {explore}."
|
||||||
|
step5_3: "Per seguire altrə utenti, clicca sul loro avatar per aprire la pagina\
|
||||||
|
\ di profilo dove puoi premere il pulsante \"Seguire\"."
|
||||||
|
step5_4: "Alcunə utenti scelgono di confermare manualmente le richieste di follow\
|
||||||
|
\ che ricevono, quindi a seconda delle persone potrebbe volerci un pò prima che\
|
||||||
|
\ la tua richiesta sia accolta."
|
||||||
|
step6_1: "Ora, se puoi visualizzare le note di altrə utenti sulla tua timeline,\
|
||||||
|
\ ce l'hai fatta!"
|
||||||
|
step6_2: "Puoi inviare una risposta rapida alle note di altrə utenti mandando loro\
|
||||||
|
\ \"reazioni\"."
|
||||||
|
step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji\
|
||||||
|
\ che vuoi mandare."
|
||||||
|
step7_1: "Complimenti! Sei arrivat@ alla fine dell'esercitazione di base su come\
|
||||||
|
\ usare FoundKey."
|
||||||
|
step7_2: "Se vuoi saperne di più su FoundKey, puoi dare un'occhiata alla sezione\
|
||||||
|
\ {help}."
|
||||||
|
step7_3: "Da ultimo, buon divertimento su FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
registerDevice: "Aggiungi dispositivo"
|
registerDevice: "Aggiungi dispositivo"
|
||||||
_permissions:
|
_permissions:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
_lang_: "日本語"
|
_lang_: "日本語"
|
||||||
|
|
||||||
headlineMisskey: "ノートでつながるネットワーク"
|
headlineMisskey: "ノートでつながるネットワーク"
|
||||||
introMisskey: "ようこそ!FoundKeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n\
|
introMisskey: "ようこそ!FoundKeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう\U0001F4E1\
|
||||||
「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
|
\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます\U0001F44D\n新しい世界を探検しよう\U0001F680"
|
||||||
monthAndDay: "{month}月 {day}日"
|
monthAndDay: "{month}月 {day}日"
|
||||||
search: "検索"
|
search: "検索"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
|
@ -31,6 +31,9 @@ signup: "新規登録"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
addUser: "ユーザーを追加"
|
addUser: "ユーザーを追加"
|
||||||
|
favorite: "お気に入り"
|
||||||
|
favorites: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
copyContent: "内容をコピー"
|
copyContent: "内容をコピー"
|
||||||
|
@ -213,6 +216,7 @@ uploadFromUrl: "URLアップロード"
|
||||||
uploadFromUrlDescription: "アップロードしたいファイルのURL"
|
uploadFromUrlDescription: "アップロードしたいファイルのURL"
|
||||||
uploadFromUrlRequested: "アップロードをリクエストしました"
|
uploadFromUrlRequested: "アップロードをリクエストしました"
|
||||||
uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
|
uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
|
||||||
|
explore: "みつける"
|
||||||
messageRead: "既読"
|
messageRead: "既読"
|
||||||
noMoreHistory: "これより過去の履歴はありません"
|
noMoreHistory: "これより過去の履歴はありません"
|
||||||
startMessaging: "チャットを開始"
|
startMessaging: "チャットを開始"
|
||||||
|
@ -291,6 +295,8 @@ inMb: "メガバイト単位"
|
||||||
iconUrl: "アイコン画像のURL (faviconなど)"
|
iconUrl: "アイコン画像のURL (faviconなど)"
|
||||||
bannerUrl: "バナー画像のURL"
|
bannerUrl: "バナー画像のURL"
|
||||||
backgroundImageUrl: "背景画像のURL"
|
backgroundImageUrl: "背景画像のURL"
|
||||||
|
pinnedUsers: "ピン留めユーザー"
|
||||||
|
pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。"
|
||||||
hcaptchaSiteKey: "サイトキー"
|
hcaptchaSiteKey: "サイトキー"
|
||||||
hcaptchaSecretKey: "シークレットキー"
|
hcaptchaSecretKey: "シークレットキー"
|
||||||
recaptchaSiteKey: "サイトキー"
|
recaptchaSiteKey: "サイトキー"
|
||||||
|
@ -314,6 +320,11 @@ silence: "サイレンス"
|
||||||
silenceConfirm: "サイレンスしますか?"
|
silenceConfirm: "サイレンスしますか?"
|
||||||
unsilence: "サイレンス解除"
|
unsilence: "サイレンス解除"
|
||||||
unsilenceConfirm: "サイレンス解除しますか?"
|
unsilenceConfirm: "サイレンス解除しますか?"
|
||||||
|
popularUsers: "人気のユーザー"
|
||||||
|
recentlyUpdatedUsers: "最近投稿したユーザー"
|
||||||
|
recentlyRegisteredUsers: "最近登録したユーザー"
|
||||||
|
recentlyDiscoveredUsers: "最近発見されたユーザー"
|
||||||
|
popularTags: "人気のタグ"
|
||||||
userList: "リスト"
|
userList: "リスト"
|
||||||
aboutMisskey: "FoundKeyについて"
|
aboutMisskey: "FoundKeyについて"
|
||||||
administrator: "管理者"
|
administrator: "管理者"
|
||||||
|
@ -354,6 +365,7 @@ messagingWithGroup: "グループでチャット"
|
||||||
title: "タイトル"
|
title: "タイトル"
|
||||||
text: "テキスト"
|
text: "テキスト"
|
||||||
enable: "有効にする"
|
enable: "有効にする"
|
||||||
|
next: "次"
|
||||||
retype: "再入力"
|
retype: "再入力"
|
||||||
noteOf: "{user}のノート"
|
noteOf: "{user}のノート"
|
||||||
inviteToGroup: "グループに招待"
|
inviteToGroup: "グループに招待"
|
||||||
|
@ -405,8 +417,8 @@ showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを
|
||||||
objectStorage: "オブジェクトストレージ"
|
objectStorage: "オブジェクトストレージ"
|
||||||
useObjectStorage: "オブジェクトストレージを使用"
|
useObjectStorage: "オブジェクトストレージを使用"
|
||||||
objectStorageBaseUrl: "Base URL"
|
objectStorageBaseUrl: "Base URL"
|
||||||
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL。\nS3: 'https://<bucket>.s3.amazonaws.com'、GCS等:
|
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL。\nS3: 'https://<bucket>.s3.amazonaws.com'、GCS等:\
|
||||||
'https://storage.googleapis.com/<bucket>'。"
|
\ 'https://storage.googleapis.com/<bucket>'。"
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
|
objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
|
||||||
objectStoragePrefix: "Prefix"
|
objectStoragePrefix: "Prefix"
|
||||||
|
@ -528,6 +540,7 @@ abuseReports: "通報"
|
||||||
reportAbuse: "通報"
|
reportAbuse: "通報"
|
||||||
reportAbuseOf: "{name}を通報する"
|
reportAbuseOf: "{name}を通報する"
|
||||||
fillAbuseReportDescription: "通報理由の詳細を記入してください。"
|
fillAbuseReportDescription: "通報理由の詳細を記入してください。"
|
||||||
|
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
|
||||||
reporter: "通報者"
|
reporter: "通報者"
|
||||||
reporteeOrigin: "通報先"
|
reporteeOrigin: "通報先"
|
||||||
reporterOrigin: "通報元"
|
reporterOrigin: "通報元"
|
||||||
|
@ -574,6 +587,7 @@ loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||||
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
||||||
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
||||||
emailVerified: "メールアドレスが確認されました"
|
emailVerified: "メールアドレスが確認されました"
|
||||||
|
noteFavoritesCount: "お気に入りノートの数"
|
||||||
pageLikesCount: "Pageにいいねした数"
|
pageLikesCount: "Pageにいいねした数"
|
||||||
pageLikedCount: "Pageにいいねされた数"
|
pageLikedCount: "Pageにいいねされた数"
|
||||||
contact: "連絡先"
|
contact: "連絡先"
|
||||||
|
@ -738,7 +752,6 @@ _ffVisibility:
|
||||||
followers: "フォロワーだけに公開"
|
followers: "フォロワーだけに公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
|
||||||
nobody: 誰にも見せない (あなたにさえも)
|
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "ほとんど完了です"
|
almostThere: "ほとんど完了です"
|
||||||
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
|
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
|
||||||
|
@ -928,6 +941,30 @@ _time:
|
||||||
hour: "時間"
|
hour: "時間"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
|
||||||
|
_tutorial:
|
||||||
|
title: "FoundKeyの使い方"
|
||||||
|
step1_1: "ようこそ!"
|
||||||
|
step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
|
||||||
|
step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
|
||||||
|
step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
|
||||||
|
step2_2: "あなたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなります。"
|
||||||
|
step3_1: "プロフィール設定はうまくできましたか?"
|
||||||
|
step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
|
||||||
|
step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
|
||||||
|
step3_4: "内容が思いつかない?「FoundKey始めました」というのはいかがでしょう!"
|
||||||
|
step4_1: "投稿できましたか?"
|
||||||
|
step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
|
||||||
|
step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
|
||||||
|
step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます!"
|
||||||
|
step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
|
||||||
|
step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
|
||||||
|
step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
|
||||||
|
step6_2: "他の人のノートには、「リアクション」を付けることができ、簡単にあなたの反応を伝えられます。"
|
||||||
|
step6_3: "リアクションを付けるには、ノートの「+」マークをクリックして、好きなリアクションを選択します。"
|
||||||
|
step7_1: "これで、FoundKeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
|
||||||
|
step7_2: "もっとFoundKeyについて知りたいときは、{help}を見てみてください。"
|
||||||
|
step7_3: "では、FoundKeyをお楽しみください\U0001F680"
|
||||||
|
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "既に設定は完了しています。"
|
alreadyRegistered: "既に設定は完了しています。"
|
||||||
registerDevice: "デバイスを登録"
|
registerDevice: "デバイスを登録"
|
||||||
|
@ -1123,7 +1160,6 @@ _timelines:
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
|
||||||
shuffled: シャッフル
|
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "ページの作成"
|
newPage: "ページの作成"
|
||||||
editPage: "ページの編集"
|
editPage: "ページの編集"
|
||||||
|
@ -1190,7 +1226,6 @@ _notification:
|
||||||
app: "連携アプリからの通知"
|
app: "連携アプリからの通知"
|
||||||
|
|
||||||
move: 自分以外のアカウントの引っ越し
|
move: 自分以外のアカウントの引っ越し
|
||||||
update: ウォッチ中のノートが更新された
|
|
||||||
_actions:
|
_actions:
|
||||||
followBack: "フォローバック"
|
followBack: "フォローバック"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
|
|
804
locales/ja-KS.yml
Normal file
804
locales/ja-KS.yml
Normal file
|
@ -0,0 +1,804 @@
|
||||||
|
_lang_: "日本語 (関西弁)"
|
||||||
|
headlineMisskey: "ノートでつながるネットワーク"
|
||||||
|
introMisskey: "ようお越し!FoundKeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう\U0001F4E1\
|
||||||
|
\n「リアクション」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか\U0001F680"
|
||||||
|
monthAndDay: "{month}月 {day}日"
|
||||||
|
search: "探す"
|
||||||
|
notifications: "通知"
|
||||||
|
username: "ユーザー名"
|
||||||
|
password: "パスワード"
|
||||||
|
forgotPassword: "パスワード忘れてん"
|
||||||
|
fetchingAsApObject: "今ちと連合に照会しとるで"
|
||||||
|
ok: "OKや"
|
||||||
|
gotIt: "ほい"
|
||||||
|
cancel: "やめとく"
|
||||||
|
renotedBy: "{user}がRenote"
|
||||||
|
noNotes: "ノートはあらへん"
|
||||||
|
noNotifications: "通知はあらへん"
|
||||||
|
instance: "インスタンス"
|
||||||
|
settings: "設定"
|
||||||
|
basicSettings: "基本設定"
|
||||||
|
otherSettings: "その他の設定"
|
||||||
|
openInWindow: "ウィンドウで開くで"
|
||||||
|
profile: "プロフィール"
|
||||||
|
timeline: "タイムライン"
|
||||||
|
noAccountDescription: "自己紹介食ってもた"
|
||||||
|
login: "ログイン"
|
||||||
|
loggingIn: "ログインしよるで"
|
||||||
|
logout: "ログアウト"
|
||||||
|
signup: "新規登録"
|
||||||
|
save: "保存"
|
||||||
|
users: "ユーザー"
|
||||||
|
addUser: "ユーザーを追加や"
|
||||||
|
favorite: "お気に入り"
|
||||||
|
favorites: "お気に入り"
|
||||||
|
unfavorite: "やっぱ気に入らん"
|
||||||
|
pin: "ピン留めしとく"
|
||||||
|
unpin: "やっぱピン留めせん"
|
||||||
|
copyContent: "内容をコピー"
|
||||||
|
copyLink: "リンクをコピー"
|
||||||
|
delete: "ほかす"
|
||||||
|
deleteAndEdit: "ほかして直す"
|
||||||
|
deleteAndEditConfirm: "このノートをほかして書き直すんか?このノートへのリアクション、Renote、返信も全部消えてまうで。"
|
||||||
|
addToList: "リストに入れたる"
|
||||||
|
sendMessage: "メッセージを送る"
|
||||||
|
copyUsername: "ユーザー名をコピー"
|
||||||
|
reply: "返事"
|
||||||
|
loadMore: "まだまだあるで!"
|
||||||
|
showMore: "まだまだあるで!"
|
||||||
|
youGotNewFollower: "フォローされたで"
|
||||||
|
receiveFollowRequest: "フォローリクエストされたで"
|
||||||
|
followRequestAccepted: "フォローが承認されたで"
|
||||||
|
mention: "メンション"
|
||||||
|
mentions: "うち宛て"
|
||||||
|
directNotes: "ダイレクト投稿"
|
||||||
|
importAndExport: "インポートとエクスポート"
|
||||||
|
import: "インポート"
|
||||||
|
export: "エクスポート"
|
||||||
|
files: "ファイル"
|
||||||
|
download: "ダウンロード"
|
||||||
|
driveFileDeleteConfirm: "ファイル「{name}」を消してしもうてええか?このファイルを添付したノートも消えてまうで。"
|
||||||
|
unfollowConfirm: "{name}のフォローを解除してもええんか?"
|
||||||
|
exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
|
||||||
|
importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。"
|
||||||
|
lists: "リスト"
|
||||||
|
note: "ノート"
|
||||||
|
notes: "ノート"
|
||||||
|
following: "フォロー"
|
||||||
|
followers: "フォロワー"
|
||||||
|
followsYou: "フォローされとるで"
|
||||||
|
createList: "リスト作る"
|
||||||
|
manageLists: "リストの管理"
|
||||||
|
error: "エラー"
|
||||||
|
somethingHappened: "なんかアカンことが起こったで"
|
||||||
|
retry: "もっぺんやる?"
|
||||||
|
pageLoadError: "ページの読み込みに失敗してしもうたで…"
|
||||||
|
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
|
||||||
|
enterListName: "リスト名を入れてや"
|
||||||
|
privacy: "プライバシー"
|
||||||
|
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
|
||||||
|
defaultNoteVisibility: "もとからの公開範囲"
|
||||||
|
follow: "フォロー"
|
||||||
|
followRequest: "フォローを頼む"
|
||||||
|
followRequests: "フォロー申請"
|
||||||
|
unfollow: "フォローやめる"
|
||||||
|
followRequestPending: "フォロー許してくれるん待っとる"
|
||||||
|
renote: "Renote"
|
||||||
|
unrenote: "Renoteやめる"
|
||||||
|
quote: "引用"
|
||||||
|
pinnedNote: "ピン留めされとるノート"
|
||||||
|
you: "あんた"
|
||||||
|
clickToShow: "押したら見えるで"
|
||||||
|
sensitive: "ちょっとアカンやつやで"
|
||||||
|
add: "増やす"
|
||||||
|
reaction: "リアクション"
|
||||||
|
reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
|
||||||
|
attachCancel: "のっけるのやめる"
|
||||||
|
markAsSensitive: "ちょっとこれはアカン"
|
||||||
|
unmarkAsSensitive: "そこまでアカンことないやろ"
|
||||||
|
enterFileName: "ファイル名を入れてや"
|
||||||
|
mute: "ミュート"
|
||||||
|
unmute: "ミュートやめたる"
|
||||||
|
block: "ブロック"
|
||||||
|
unblock: "ブロックやめたる"
|
||||||
|
suspend: "凍結"
|
||||||
|
unsuspend: "溶かす"
|
||||||
|
blockConfirm: "ブロックしてもええんか?"
|
||||||
|
unblockConfirm: "ブロックやめたるってほんまか?"
|
||||||
|
suspendConfirm: "凍結してしもうてええか?"
|
||||||
|
unsuspendConfirm: "解凍するけどええか?"
|
||||||
|
selectList: "リストを選ぶ"
|
||||||
|
selectAntenna: "アンテナを選ぶ"
|
||||||
|
selectWidget: "ウィジェットを選ぶ"
|
||||||
|
editWidgets: "ウィジェットをいじる"
|
||||||
|
editWidgetsExit: "編集終ったで"
|
||||||
|
customEmojis: "カスタム絵文字"
|
||||||
|
emoji: "絵文字"
|
||||||
|
emojis: "絵文字"
|
||||||
|
addEmoji: "絵文字を追加"
|
||||||
|
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||||
|
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。"
|
||||||
|
flagAsBot: "Botやで"
|
||||||
|
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、FoundKeyのシステム上での扱いがBotに合ったもんになるんやで。"
|
||||||
|
flagAsCat: "Catやで"
|
||||||
|
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?"
|
||||||
|
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
|
||||||
|
addAccount: "アカウントを追加"
|
||||||
|
loginFailed: "ログインに失敗してしもうた…"
|
||||||
|
showOnRemote: "リモートで見る"
|
||||||
|
general: "全般"
|
||||||
|
setWallpaper: "壁紙を設定"
|
||||||
|
removeWallpaper: "壁紙を削除"
|
||||||
|
youHaveNoLists: "リストがあらへんで?"
|
||||||
|
followConfirm: "{name}をフォローしてええか?"
|
||||||
|
proxyAccount: "プロキシアカウント"
|
||||||
|
proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…"
|
||||||
|
host: "ホスト"
|
||||||
|
selectUser: "ユーザーを選ぶ"
|
||||||
|
recipient: "宛先"
|
||||||
|
annotation: "注釈"
|
||||||
|
federation: "連合"
|
||||||
|
registeredAt: "初観測"
|
||||||
|
latestRequestSentAt: "ちょっと前のリクエスト送信"
|
||||||
|
latestRequestReceivedAt: "ちょっと前のリクエスト受信"
|
||||||
|
latestStatus: "ちょっと前のステータス"
|
||||||
|
charts: "チャート"
|
||||||
|
perHour: "1時間ごと"
|
||||||
|
perDay: "1日ごと"
|
||||||
|
stopActivityDelivery: "アクティビティの配送をやめる"
|
||||||
|
blockThisInstance: "このインスタンスをブロック"
|
||||||
|
software: "ソフトウェア"
|
||||||
|
version: "バージョン"
|
||||||
|
withNFiles: "{n}個のファイル"
|
||||||
|
jobQueue: "ジョブキュー"
|
||||||
|
instanceInfo: "インスタンス情報"
|
||||||
|
statistics: "統計"
|
||||||
|
clearQueue: "キューにさいなら"
|
||||||
|
clearQueueConfirmTitle: "キューをクリアしまっか?"
|
||||||
|
clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。"
|
||||||
|
clearCachedFiles: "キャッシュにさいなら"
|
||||||
|
clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?"
|
||||||
|
blockedInstances: "インスタンスブロック"
|
||||||
|
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定してな。ブロックされてもうたインスタンスとはもう金輪際やり取りできひんくなるで。"
|
||||||
|
muteAndBlock: "ミュートとブロック"
|
||||||
|
mutedUsers: "ミュートしたユーザー"
|
||||||
|
blockedUsers: "ブロックしたユーザー"
|
||||||
|
noUsers: "ユーザーはおらへん"
|
||||||
|
editProfile: "プロフィールをいじる"
|
||||||
|
noteDeleteConfirm: "このノートを削除しまっか?"
|
||||||
|
pinLimitExceeded: "これ以上ピン留めできひん"
|
||||||
|
intro: "FoundKeyのインストールが完了してん!管理者アカウントを作ってや。"
|
||||||
|
done: "でけた"
|
||||||
|
processing: "処理しとる"
|
||||||
|
preview: "プレビュー"
|
||||||
|
default: "デフォルト"
|
||||||
|
noCustomEmojis: "絵文字はあらへん"
|
||||||
|
noJobs: "ジョブはあらへん"
|
||||||
|
federating: "連合しとる"
|
||||||
|
blocked: "ブロックしとる"
|
||||||
|
suspended: "配信せぇへん"
|
||||||
|
all: "みんな"
|
||||||
|
subscribing: "購読しとる"
|
||||||
|
publishing: "配信しとる"
|
||||||
|
notResponding: "応答してへんで"
|
||||||
|
changePassword: "パスワード変える"
|
||||||
|
security: "セキュリティ"
|
||||||
|
retypedNotMatch: "そやないねん。"
|
||||||
|
currentPassword: "今のパスワード"
|
||||||
|
newPassword: "今度のパスワード"
|
||||||
|
newPasswordRetype: "今度のパスワード(もっぺん入れて)"
|
||||||
|
attachFile: "ファイルのっける"
|
||||||
|
more: "他のやつ!"
|
||||||
|
featured: "ハイライト"
|
||||||
|
usernameOrUserId: "ユーザー名かユーザーID"
|
||||||
|
noSuchUser: "ユーザーが見つからへんで"
|
||||||
|
lookup: "見てきて"
|
||||||
|
announcements: "お知らせ"
|
||||||
|
imageUrl: "画像URL"
|
||||||
|
remove: "ほかす"
|
||||||
|
removeAreYouSure: "「{x}」はほかしてええか?"
|
||||||
|
deleteAreYouSure: "「{x}」はほかしてええか?"
|
||||||
|
resetAreYouSure: "リセットしてええん?"
|
||||||
|
saved: "保存したで!"
|
||||||
|
messaging: "チャット"
|
||||||
|
upload: "アップロード"
|
||||||
|
fromDrive: "ドライブから"
|
||||||
|
fromUrl: "URLから"
|
||||||
|
uploadFromUrl: "URLアップロード"
|
||||||
|
uploadFromUrlDescription: "このURLのファイルをアップロードしたいねん"
|
||||||
|
uploadFromUrlRequested: "アップロードしたい言うといたで"
|
||||||
|
uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。"
|
||||||
|
explore: "みつける"
|
||||||
|
messageRead: "もう読んだ"
|
||||||
|
noMoreHistory: "これより過去の履歴はあらへんで"
|
||||||
|
startMessaging: "チャットやるで"
|
||||||
|
nUsersRead: "{n}人が読んでもうた"
|
||||||
|
agreeTo: "{0}に同意したで"
|
||||||
|
tos: "利用規約"
|
||||||
|
start: "始める"
|
||||||
|
home: "ホーム"
|
||||||
|
remoteUserCaution: "リモートユーザーやから、足りひん情報あるかもしれへん。"
|
||||||
|
activity: "アクティビティ"
|
||||||
|
images: "画像"
|
||||||
|
birthday: "生まれた日"
|
||||||
|
yearsOld: "{age}歳"
|
||||||
|
registeredDate: "始めた日"
|
||||||
|
location: "場所"
|
||||||
|
theme: "テーマ"
|
||||||
|
themeForLightMode: "ライトモードではこのテーマつこて"
|
||||||
|
themeForDarkMode: "ダークモードではこのテーマつこて"
|
||||||
|
light: "ライト"
|
||||||
|
dark: "ダーク"
|
||||||
|
lightThemes: "デイゲーム"
|
||||||
|
darkThemes: "ナイトゲーム"
|
||||||
|
syncDeviceDarkMode: "デバイスのダークモードと一緒にする"
|
||||||
|
drive: "ドライブ"
|
||||||
|
selectFile: "ファイル選んでや"
|
||||||
|
selectFiles: "ファイル選んでや"
|
||||||
|
selectFolder: "フォルダ選んでや"
|
||||||
|
selectFolders: "フォルダ選んでや"
|
||||||
|
renameFile: "ファイル名をいらう"
|
||||||
|
folderName: "フォルダー名"
|
||||||
|
createFolder: "フォルダー作る"
|
||||||
|
renameFolder: "フォルダー名を変える"
|
||||||
|
deleteFolder: "フォルダーを消してまう"
|
||||||
|
addFile: "ファイルを追加"
|
||||||
|
unableToDelete: "消そうおもってんけどな、あかんかったわ"
|
||||||
|
inputNewFileName: "今度のファイル名は何にするん?"
|
||||||
|
inputNewDescription: "新しいキャプションを入力しましょ"
|
||||||
|
inputNewFolderName: "今度のフォルダ名は何にするん?"
|
||||||
|
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
|
||||||
|
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん"
|
||||||
|
copyUrl: "URLをコピー"
|
||||||
|
rename: "名前を変えるで"
|
||||||
|
avatar: "アイコン"
|
||||||
|
banner: "バナー"
|
||||||
|
nsfw: "閲覧注意"
|
||||||
|
whenServerDisconnected: "サーバーとの接続が切れたとき"
|
||||||
|
disconnectedFromServer: "サーバーとの通信が切れたで"
|
||||||
|
reload: "リロード"
|
||||||
|
doNothing: "何もせんとく"
|
||||||
|
reloadConfirm: "リロードしてええか?"
|
||||||
|
watch: "ウォッチ"
|
||||||
|
unwatch: "ウォッチやめる"
|
||||||
|
accept: "ええで"
|
||||||
|
reject: "あかん"
|
||||||
|
normal: "ええ感じ"
|
||||||
|
instanceName: "インスタンス名"
|
||||||
|
instanceDescription: "インスタンスの紹介"
|
||||||
|
maintainerName: "管理者の名前"
|
||||||
|
maintainerEmail: "管理者のメールアドレス"
|
||||||
|
tosUrl: "利用規約のURL"
|
||||||
|
thisYear: "今年"
|
||||||
|
thisMonth: "今月"
|
||||||
|
today: "今日"
|
||||||
|
dayX: "{day}日"
|
||||||
|
monthX: "{month}月"
|
||||||
|
yearX: "{year}年"
|
||||||
|
pages: "ページ"
|
||||||
|
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
|
||||||
|
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
|
||||||
|
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"
|
||||||
|
enableRegistration: "一見さんでも誰でもいらっしゃ~い"
|
||||||
|
invite: "来てや"
|
||||||
|
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
|
||||||
|
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
|
||||||
|
inMb: "メガバイト単位"
|
||||||
|
iconUrl: "アイコン画像のURL"
|
||||||
|
bannerUrl: "バナー画像のURL"
|
||||||
|
pinnedUsers: "ピン留めしたユーザー"
|
||||||
|
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。"
|
||||||
|
hcaptchaSiteKey: "サイトキー"
|
||||||
|
hcaptchaSecretKey: "シークレットキー"
|
||||||
|
recaptchaSiteKey: "サイトキー"
|
||||||
|
recaptchaSecretKey: "シークレットキー"
|
||||||
|
antennas: "アンテナ"
|
||||||
|
manageAntennas: "アンテナいじる"
|
||||||
|
name: "名前"
|
||||||
|
antennaSource: "受信ソース(このソースは食われへん)"
|
||||||
|
antennaKeywords: "受信キーワード"
|
||||||
|
antennaExcludeKeywords: "除外キーワード"
|
||||||
|
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
|
||||||
|
notifyAntenna: "新しいノートを通知すんで"
|
||||||
|
withFileAntenna: "なんか添付されたノートだけ"
|
||||||
|
antennaUsersDescription: "ユーザー名を改行で区切ったってな"
|
||||||
|
caseSensitive: "大文字と小文字は別もんや"
|
||||||
|
withReplies: "返信も入れたって"
|
||||||
|
connectedTo: "次のアカウントに繋がっとるで"
|
||||||
|
notesAndReplies: "投稿と返信"
|
||||||
|
withFiles: "ファイル付いとる"
|
||||||
|
silence: "サイレンス"
|
||||||
|
silenceConfirm: "サイレンスしよか?"
|
||||||
|
unsilence: "サイレンスやめるで"
|
||||||
|
unsilenceConfirm: "サイレンスやめよか?"
|
||||||
|
popularUsers: "人気のユーザー"
|
||||||
|
recentlyUpdatedUsers: "ちょっと前に投稿したばっかりのユーザー"
|
||||||
|
recentlyRegisteredUsers: "ちょっと前に始めたばっかりのユーザー"
|
||||||
|
recentlyDiscoveredUsers: "最近見っけたユーザー"
|
||||||
|
popularTags: "人気のタグ"
|
||||||
|
userList: "リスト"
|
||||||
|
aboutMisskey: "FoundKeyってなんや?"
|
||||||
|
administrator: "管理者"
|
||||||
|
token: "トークン"
|
||||||
|
twoStepAuthentication: "二段階認証"
|
||||||
|
moderator: "モデレーター"
|
||||||
|
nUsersMentioned: "{n}人が投稿"
|
||||||
|
securityKey: "セキュリティキー"
|
||||||
|
securityKeyName: "キーの名前"
|
||||||
|
registerSecurityKey: "セキュリティキーを登録するで"
|
||||||
|
lastUsed: "最後につこうた日"
|
||||||
|
unregister: "登録やめる"
|
||||||
|
passwordLessLogin: "パスワード無くてもログインできるようにする"
|
||||||
|
resetPassword: "パスワードをリセット"
|
||||||
|
newPasswordIs: "今度のパスワードは「{password}」や"
|
||||||
|
reduceUiAnimation: "UIの動きやアニメーションを減らす"
|
||||||
|
share: "わけわけ"
|
||||||
|
notFound: "見つからへんね"
|
||||||
|
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
||||||
|
uploadFolder: "とりあえずアップロードしたやつ置いとく所"
|
||||||
|
markAsReadAllNotifications: "通知はもう全て読んだわっ"
|
||||||
|
markAsReadAllUnreadNotes: "投稿は全て読んだわっ"
|
||||||
|
markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ"
|
||||||
|
help: "ヘルプ"
|
||||||
|
inputMessageHere: "ここにメッセージ書いてや"
|
||||||
|
close: "閉じる"
|
||||||
|
group: "グループ"
|
||||||
|
groups: "グループ"
|
||||||
|
createGroup: "グループを作るで"
|
||||||
|
ownedGroups: "所有しとるグループ"
|
||||||
|
joinedGroups: "参加しとるグループ"
|
||||||
|
invites: "来てや"
|
||||||
|
groupName: "グループ名"
|
||||||
|
members: "メンバー"
|
||||||
|
transfer: "譲渡"
|
||||||
|
messagingWithUser: "ユーザーとチャット"
|
||||||
|
messagingWithGroup: "グループでチャット"
|
||||||
|
title: "タイトル"
|
||||||
|
text: "テキスト"
|
||||||
|
enable: "有効にするで"
|
||||||
|
next: "次"
|
||||||
|
retype: "もっかい入力"
|
||||||
|
noteOf: "{user}のノート"
|
||||||
|
inviteToGroup: "グループに招く"
|
||||||
|
quoteAttached: "引用付いとるで"
|
||||||
|
quoteQuestion: "引用として添付してもええか?"
|
||||||
|
noMessagesYet: "まだチャットはあらへんで"
|
||||||
|
newMessageExists: "新しいメッセージがきたで"
|
||||||
|
onlyOneFileCanBeAttached: "すまん、メッセージに添付できるファイルはひとつだけなんや。"
|
||||||
|
signinRequired: "ログインしてくれへん?"
|
||||||
|
invitationCode: "招待コード"
|
||||||
|
checking: "確認しとるで"
|
||||||
|
available: "利用できる"
|
||||||
|
unavailable: "利用できん"
|
||||||
|
usernameInvalidFormat: "a~z、A~Z、0~9、_が使えるで"
|
||||||
|
tooShort: "短すぎやろ!"
|
||||||
|
tooLong: "長すぎやろ!"
|
||||||
|
weakPassword: "へぼいパスワード"
|
||||||
|
normalPassword: "普通のパスワード"
|
||||||
|
strongPassword: "ええ感じのパスワード"
|
||||||
|
passwordMatched: "よし!一致や!"
|
||||||
|
passwordNotMatched: "一致しとらんで?"
|
||||||
|
or: "それか"
|
||||||
|
language: "言語"
|
||||||
|
uiLanguage: "UIの表示言語"
|
||||||
|
groupInvited: "グループに招待されとるで"
|
||||||
|
useOsNativeEmojis: "OSネイティブの絵文字を使う"
|
||||||
|
youHaveNoGroups: "グループがあらへんねぇ。"
|
||||||
|
noHistory: "履歴はあらへんねぇ。"
|
||||||
|
signinHistory: "ログイン履歴"
|
||||||
|
disableAnimatedMfm: "動きがやかましいMFMを止める"
|
||||||
|
category: "カテゴリ"
|
||||||
|
tags: "タグ"
|
||||||
|
createAccount: "アカウントを作成"
|
||||||
|
fontSize: "フォントサイズ"
|
||||||
|
noFollowRequests: "フォロー申請はあらへんで"
|
||||||
|
openImageInNewTab: "画像を新しいタブで開く"
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
local: "ローカル"
|
||||||
|
remote: "リモート"
|
||||||
|
dayOverDayChanges: "前日比"
|
||||||
|
appearance: "見た目"
|
||||||
|
clientSettings: "クライアントの設定"
|
||||||
|
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示してや"
|
||||||
|
objectStorage: "オブジェクトストレージ"
|
||||||
|
useObjectStorage: "オブジェクトストレージを使う"
|
||||||
|
objectStorageBaseUrl: "Base URL"
|
||||||
|
objectStorageBaseUrlDesc: "参照に使うにURLやで。CDNやProxyを使用してるんならそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCSとかなら:\
|
||||||
|
\ 'https://storage.googleapis.com/<bucket>'。"
|
||||||
|
objectStorageBucket: "Bucket"
|
||||||
|
objectStoragePrefix: "Prefix"
|
||||||
|
objectStorageEndpoint: "Endpoint"
|
||||||
|
objectStorageRegion: "Region"
|
||||||
|
objectStorageUseSSL: "SSLを使う"
|
||||||
|
objectStorageUseProxy: "Proxyを使う"
|
||||||
|
objectStorageUseProxyDesc: "API接続にproxy使わんのやったら切ってくれへん?"
|
||||||
|
objectStorageSetPublicRead: "アップロードした時に'public-read'を設定してや"
|
||||||
|
showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?"
|
||||||
|
newNoteRecived: "新しいノートがあるで"
|
||||||
|
sounds: "サウンド"
|
||||||
|
listen: "聴く"
|
||||||
|
none: "なし"
|
||||||
|
showInPage: "ページで表示"
|
||||||
|
popout: "ポップアウト"
|
||||||
|
volume: "音量"
|
||||||
|
masterVolume: "全体の音量"
|
||||||
|
details: "もっと"
|
||||||
|
unableToProcess: "なんか作業が止まってしまったようやね"
|
||||||
|
recentUsed: "最近使ったやつ"
|
||||||
|
install: "インストール"
|
||||||
|
uninstall: "アンインストール"
|
||||||
|
installedApps: "インストールされとるアプリ"
|
||||||
|
nothing: "あらへん"
|
||||||
|
installedDate: "インストールした日時"
|
||||||
|
lastUsedDate: "最後に使った日時"
|
||||||
|
state: "状態"
|
||||||
|
sort: "仕分ける"
|
||||||
|
ascendingOrder: "小さい順"
|
||||||
|
descendingOrder: "大きい順"
|
||||||
|
scratchpad: "スクラッチパッド"
|
||||||
|
scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。FoundKeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
|
||||||
|
output: "出力"
|
||||||
|
updateRemoteUser: "リモートユーザー情報の更新してくれん?"
|
||||||
|
deleteAllFilesConfirm: "ホンマにすべてのファイルを削除するん?消したもんはもう戻ってこんのやで?"
|
||||||
|
removeAllFollowing: "フォローを全解除"
|
||||||
|
removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのインスタンスが消えて無くなった時とかには便利な機能やで。"
|
||||||
|
userSuspended: "このユーザーは...凍結されとる。"
|
||||||
|
userSilenced: "このユーザーは...サイレンスされとる。"
|
||||||
|
divider: "分割線"
|
||||||
|
relays: "リレー"
|
||||||
|
addRelay: "リレーの追加"
|
||||||
|
inboxUrl: "inboxのURL"
|
||||||
|
poll: "アンケート"
|
||||||
|
enablePlayer: "プレイヤーを開く"
|
||||||
|
disablePlayer: "プレイヤーを閉じる"
|
||||||
|
themeEditor: "テーマエディター"
|
||||||
|
description: "説明"
|
||||||
|
author: "作者"
|
||||||
|
leaveConfirm: "未保存の変更があるで!ほかしてええか?"
|
||||||
|
manage: "管理"
|
||||||
|
plugins: "プラグイン"
|
||||||
|
deck: "デッキ"
|
||||||
|
width: "幅"
|
||||||
|
height: "高さ"
|
||||||
|
large: "大"
|
||||||
|
medium: "中"
|
||||||
|
small: "小"
|
||||||
|
edit: "編集"
|
||||||
|
enableEmail: "メール配信を受け取る"
|
||||||
|
emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
|
||||||
|
email: "メール"
|
||||||
|
emailAddress: "メールアドレス"
|
||||||
|
smtpConfig: "SMTP サーバーの設定"
|
||||||
|
smtpHost: "ホスト"
|
||||||
|
smtpPort: "ポート"
|
||||||
|
smtpUser: "ユーザー名"
|
||||||
|
smtpPass: "パスワード"
|
||||||
|
emptyToDisableSmtpAuth: "ユーザー名とパスワードになんも入れんかったら、SMTP認証を無効化するで"
|
||||||
|
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
||||||
|
testEmail: "配信テスト"
|
||||||
|
wordMute: "ワードミュート"
|
||||||
|
userSaysSomething: "{name}が何か言ったようやで"
|
||||||
|
makeActive: "使うで"
|
||||||
|
display: "表示"
|
||||||
|
copy: "コピー"
|
||||||
|
overview: "概要"
|
||||||
|
database: "データベース"
|
||||||
|
channel: "チャンネル"
|
||||||
|
create: "作成"
|
||||||
|
notificationSetting: "通知設定"
|
||||||
|
notificationSettingDesc: "表示する通知の種類えらんでや。"
|
||||||
|
useGlobalSetting: "グローバル設定を使ってや"
|
||||||
|
other: "その他"
|
||||||
|
regenerateLoginToken: "ログイントークンを再生成"
|
||||||
|
behavior: "動作"
|
||||||
|
abuseReports: "通報"
|
||||||
|
reportAbuse: "通報"
|
||||||
|
reportAbuseOf: "{name}を通報する"
|
||||||
|
send: "送信"
|
||||||
|
abuseMarkAsResolved: "対応したで"
|
||||||
|
openInNewTab: "新しいタブで開く"
|
||||||
|
defaultNavigationBehaviour: "デフォルトのナビゲーション"
|
||||||
|
instanceTicker: "ノートのインスタンス情報"
|
||||||
|
system: "システム"
|
||||||
|
switchUi: "UI切り替え"
|
||||||
|
desktop: "デスクトップ"
|
||||||
|
clip: "クリップ"
|
||||||
|
receivedReactionsCount: "リアクションされた数"
|
||||||
|
pollVotesCount: "アンケートに投票した数"
|
||||||
|
pollVotedCount: "アンケートに投票された数"
|
||||||
|
yes: "はい"
|
||||||
|
no: "いいえ"
|
||||||
|
driveFilesCount: "ドライブのファイル数"
|
||||||
|
emailVerified: "メールアドレスは確認されたで"
|
||||||
|
pageLikesCount: "Pageにええやんと思った数"
|
||||||
|
pageLikedCount: "Pageにええやんと思ってくれた数"
|
||||||
|
clips: "クリップ"
|
||||||
|
duplicate: "複製"
|
||||||
|
left: "左"
|
||||||
|
center: "中央"
|
||||||
|
wide: "広い"
|
||||||
|
narrow: "狭い"
|
||||||
|
reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?"
|
||||||
|
clearCache: "キャッシュをほかす"
|
||||||
|
onlineUsersCount: "{n}人が起きとるで"
|
||||||
|
backgroundColor: "背景"
|
||||||
|
accentColor: "アクセント"
|
||||||
|
textColor: "文字"
|
||||||
|
saveAs: "名前を付けて保存"
|
||||||
|
createdAt: "作成した日"
|
||||||
|
updatedAt: "更新日時"
|
||||||
|
deleteConfirm: "ホンマに削除するで?"
|
||||||
|
closeAccount: "アカウントを閉鎖する"
|
||||||
|
newVersionOfClientAvailable: "新しいバージョンのクライアントが使えるで。"
|
||||||
|
usageAmount: "使用量"
|
||||||
|
capacity: "容量"
|
||||||
|
inUse: "使用中"
|
||||||
|
editCode: "コードを編集"
|
||||||
|
apply: "適用"
|
||||||
|
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る"
|
||||||
|
emailNotification: "メール通知"
|
||||||
|
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開くようにする"
|
||||||
|
typingUsers: "{users}が今書きよるで"
|
||||||
|
jumpToSpecifiedDate: "特定の日付にジャンプ"
|
||||||
|
clear: "クリア"
|
||||||
|
markAllAsRead: "もうみな読んでもうたわ"
|
||||||
|
goBack: "戻る"
|
||||||
|
info: "情報"
|
||||||
|
user: "ユーザー"
|
||||||
|
administration: "管理"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
|
hide: "隠す"
|
||||||
|
indefinitely: "無期限"
|
||||||
|
_email:
|
||||||
|
_follow:
|
||||||
|
title: "フォローされたで"
|
||||||
|
_receiveFollowRequest:
|
||||||
|
title: "フォローリクエストを受け取ったで"
|
||||||
|
_plugin:
|
||||||
|
install: "プラグインのインストール"
|
||||||
|
installWarn: "信頼できへんプラグインはインストールせんとってな"
|
||||||
|
_registry:
|
||||||
|
scope: "スコープ"
|
||||||
|
key: "キー"
|
||||||
|
keys: "キー"
|
||||||
|
domain: "ドメイン"
|
||||||
|
createKey: "キーを作る"
|
||||||
|
_aboutMisskey:
|
||||||
|
about: "FoundKeyはsyuiloが2014年からずっと作ってはる、オープンソースなソフトウェアや。"
|
||||||
|
allContributors: "全ての貢献者"
|
||||||
|
source: "ソースコード"
|
||||||
|
_mfm:
|
||||||
|
cheatSheet: "MFMチートシート"
|
||||||
|
mention: "メンション"
|
||||||
|
hashtag: "ハッシュタグ"
|
||||||
|
url: "URL"
|
||||||
|
link: "リンク"
|
||||||
|
bold: "太字"
|
||||||
|
center: "中央寄せ"
|
||||||
|
inlineCode: "コード(インライン)"
|
||||||
|
blockCode: "コード(ブロック)"
|
||||||
|
inlineMath: "数式(インライン)"
|
||||||
|
quote: "引用"
|
||||||
|
emoji: "カスタム絵文字"
|
||||||
|
search: "探す"
|
||||||
|
shake: "アニメーション(ぶるぶる)"
|
||||||
|
twitch: "アニメーション(ブレ)"
|
||||||
|
spin: "アニメーション(回転)"
|
||||||
|
blur: "ぼかし"
|
||||||
|
font: "フォント"
|
||||||
|
rotate: "回転"
|
||||||
|
_instanceTicker:
|
||||||
|
none: "表示せん"
|
||||||
|
remote: "リモートユーザーに表示"
|
||||||
|
always: "常に表示"
|
||||||
|
_serverDisconnectedBehavior:
|
||||||
|
reload: "自動でリロード"
|
||||||
|
dialog: "ダイアログで警告"
|
||||||
|
_channel:
|
||||||
|
create: "チャンネルを作る"
|
||||||
|
edit: "チャンネルを編集"
|
||||||
|
setBanner: "バナーを設定"
|
||||||
|
removeBanner: "バナーを削除"
|
||||||
|
featured: "トレンド"
|
||||||
|
notesCount: "{n}こ投稿があるで"
|
||||||
|
_menuDisplay:
|
||||||
|
hide: "隠す"
|
||||||
|
_wordMute:
|
||||||
|
soft: "ソフト"
|
||||||
|
hard: "ハード"
|
||||||
|
_theme:
|
||||||
|
explore: "テーマを探す"
|
||||||
|
install: "テーマのインストール"
|
||||||
|
manage: "テーマの管理"
|
||||||
|
code: "テーマコード"
|
||||||
|
description: "説明"
|
||||||
|
installed: "{name}をインストールしたで。"
|
||||||
|
installedThemes: "インストールされとるテーマ"
|
||||||
|
builtinThemes: "標準のテーマ"
|
||||||
|
alreadyInstalled: "そのテーマはもうインストールされとるで?"
|
||||||
|
make: "テーマを作る"
|
||||||
|
_sfx:
|
||||||
|
note: "ノート"
|
||||||
|
noteMy: "ノート(自分)"
|
||||||
|
notification: "通知"
|
||||||
|
chat: "チャット"
|
||||||
|
_ago:
|
||||||
|
future: "未来"
|
||||||
|
justNow: "たった今"
|
||||||
|
secondsAgo: "{n}秒前"
|
||||||
|
minutesAgo: "{n}分前"
|
||||||
|
hoursAgo: "{n}時間前"
|
||||||
|
daysAgo: "{n}日前"
|
||||||
|
weeksAgo: "{n}週間前"
|
||||||
|
monthsAgo: "{n}ヶ月前"
|
||||||
|
yearsAgo: "{n}年前"
|
||||||
|
_time:
|
||||||
|
second: "秒"
|
||||||
|
minute: "分"
|
||||||
|
hour: "時間"
|
||||||
|
day: "日"
|
||||||
|
_tutorial:
|
||||||
|
step3_1: "プロフィール設定はええ感じにできたか?"
|
||||||
|
_2fa:
|
||||||
|
alreadyRegistered: "もう設定終わっとるわ。"
|
||||||
|
_permissions:
|
||||||
|
"write:votes": "投票する"
|
||||||
|
"read:pages": "ページを見る"
|
||||||
|
"read:page-likes": "ページのええやんを見る"
|
||||||
|
"write:page-likes": "ページのええやんを操作する"
|
||||||
|
"read:user-groups": "ユーザーグループを見る"
|
||||||
|
"read:channels": "チャンネルを見る"
|
||||||
|
_auth:
|
||||||
|
permissionAsk: "このアプリは次の権限を要求しとるで"
|
||||||
|
_antennaSources:
|
||||||
|
all: "みんなのノート"
|
||||||
|
homeTimeline: "フォローしとるユーザーのノート"
|
||||||
|
_weekday:
|
||||||
|
sunday: "日曜日"
|
||||||
|
monday: "月曜日"
|
||||||
|
tuesday: "火曜日"
|
||||||
|
wednesday: "水曜日"
|
||||||
|
thursday: "木曜日"
|
||||||
|
friday: "金曜日"
|
||||||
|
saturday: "土曜日"
|
||||||
|
_widgets:
|
||||||
|
memo: "付箋"
|
||||||
|
notifications: "通知"
|
||||||
|
timeline: "タイムライン"
|
||||||
|
calendar: "カレンダー"
|
||||||
|
trends: "トレンド"
|
||||||
|
clock: "時計"
|
||||||
|
rss: "RSSリーダー"
|
||||||
|
activity: "アクティビティ"
|
||||||
|
photos: "フォト"
|
||||||
|
digitalClock: "デジタル時計"
|
||||||
|
federation: "連合"
|
||||||
|
postForm: "投稿フォーム"
|
||||||
|
slideshow: "スライドショー"
|
||||||
|
button: "ボタン"
|
||||||
|
onlineUsers: "オンラインユーザー"
|
||||||
|
jobQueue: "ジョブキュー"
|
||||||
|
serverMetric: "サーバーメトリクス"
|
||||||
|
aiscript: "AiScriptコンソール"
|
||||||
|
_cw:
|
||||||
|
hide: "隠す"
|
||||||
|
show: "続き見して"
|
||||||
|
chars: "{count}文字"
|
||||||
|
files: "{count}ファイル"
|
||||||
|
_poll:
|
||||||
|
choiceN: "選択肢{n}"
|
||||||
|
noMore: "これ以上追加でけへん"
|
||||||
|
canMultipleVote: "複数回答可"
|
||||||
|
expiration: "期限"
|
||||||
|
infinite: "無期限"
|
||||||
|
at: "日時指定"
|
||||||
|
after: "経過指定"
|
||||||
|
deadlineDate: "期日"
|
||||||
|
deadlineTime: "時間"
|
||||||
|
duration: "期間"
|
||||||
|
votesCount: "{n}票"
|
||||||
|
vote: "投票する"
|
||||||
|
_visibility:
|
||||||
|
publicDescription: "みんなに公開"
|
||||||
|
home: "ホーム"
|
||||||
|
followers: "フォロワー"
|
||||||
|
_profile:
|
||||||
|
name: "名前"
|
||||||
|
username: "ユーザー名"
|
||||||
|
_exportOrImport:
|
||||||
|
allNotes: "全てのノート"
|
||||||
|
followingList: "フォロー"
|
||||||
|
muteList: "ミュート"
|
||||||
|
blockingList: "ブロック"
|
||||||
|
userLists: "リスト"
|
||||||
|
_charts:
|
||||||
|
federation: "連合"
|
||||||
|
apRequest: "リクエスト"
|
||||||
|
usersTotal: "ユーザーの合計"
|
||||||
|
activeUsers: "アクティブユーザー数"
|
||||||
|
notesIncDec: "ノートの増減"
|
||||||
|
localNotesIncDec: "ローカルのノートの増減"
|
||||||
|
remoteNotesIncDec: "リモートのノートの増減"
|
||||||
|
notesTotal: "ノートの合計"
|
||||||
|
filesIncDec: "ファイルの増減"
|
||||||
|
filesTotal: "ファイルの合計"
|
||||||
|
storageUsageIncDec: "ストレージ使用量の増減"
|
||||||
|
storageUsageTotal: "ストレージ使用量の合計"
|
||||||
|
_instanceCharts:
|
||||||
|
requests: "リクエスト"
|
||||||
|
users: "ユーザーの増減"
|
||||||
|
usersTotal: "ユーザーの累積"
|
||||||
|
notes: "ノートの増減"
|
||||||
|
notesTotal: "ノートの累積"
|
||||||
|
ff: "フォロー/フォロワーの増減"
|
||||||
|
ffTotal: "フォロー/フォロワーの累積"
|
||||||
|
cacheSize: "キャッシュサイズの増減"
|
||||||
|
cacheSizeTotal: "キャッシュサイズの累積"
|
||||||
|
files: "ファイル数の増減"
|
||||||
|
filesTotal: "ファイル数の累積"
|
||||||
|
_timelines:
|
||||||
|
home: "ホーム"
|
||||||
|
local: "ローカル"
|
||||||
|
social: "ソーシャル"
|
||||||
|
global: "グローバル"
|
||||||
|
_pages:
|
||||||
|
newPage: "ページを作る"
|
||||||
|
editPage: "ページの編集"
|
||||||
|
readPage: "ソースを表示中"
|
||||||
|
created: "ページを作成したで"
|
||||||
|
updated: "ページを更新したで"
|
||||||
|
deleted: "ページを削除したで"
|
||||||
|
pageSetting: "ページ設定"
|
||||||
|
viewPage: "ページを見る"
|
||||||
|
like: "ええやん"
|
||||||
|
unlike: "良くないわ"
|
||||||
|
liked: "ええと思ったページ"
|
||||||
|
contents: "コンテンツ"
|
||||||
|
summary: "ページの要約"
|
||||||
|
alignCenter: "中央寄せ"
|
||||||
|
font: "フォント"
|
||||||
|
fontSerif: "セリフ"
|
||||||
|
fontSansSerif: "サンセリフ"
|
||||||
|
eyeCatchingImageSet: "アイキャッチ画像を設定"
|
||||||
|
eyeCatchingImageRemove: "アイキャッチ画像を削除"
|
||||||
|
_notification:
|
||||||
|
youGotMention: "{name}からのメンション"
|
||||||
|
youGotReply: "{name}からのリプライ"
|
||||||
|
youWereFollowed: "フォローされたで"
|
||||||
|
youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
|
||||||
|
yourFollowRequestAccepted: "フォローさせてもろたで"
|
||||||
|
youWereInvitedToGroup: "グループに招待されとるで"
|
||||||
|
_types:
|
||||||
|
follow: "フォロー"
|
||||||
|
mention: "メンション"
|
||||||
|
renote: "Renote"
|
||||||
|
quote: "引用"
|
||||||
|
reaction: "リアクション"
|
||||||
|
receiveFollowRequest: "フォロー許可してほしいみたいやで"
|
||||||
|
followRequestAccepted: "フォローが受理されたで"
|
||||||
|
_actions:
|
||||||
|
reply: "返事"
|
||||||
|
renote: "Renote"
|
||||||
|
_deck:
|
||||||
|
alwaysShowMainColumn: "いつもメインカラムを表示"
|
||||||
|
columnAlign: "カラムの寄せ"
|
||||||
|
columnMargin: "カラム間のマージン"
|
||||||
|
columnHeaderHeight: "カラムのヘッダー幅"
|
||||||
|
addColumn: "カラムを追加"
|
||||||
|
swapLeft: "左に移動"
|
||||||
|
swapRight: "右に移動"
|
||||||
|
swapUp: "上に移動"
|
||||||
|
swapDown: "下に移動"
|
||||||
|
stackLeft: "左に重ねる"
|
||||||
|
popRight: "右に出す"
|
||||||
|
profile: "プロファイル"
|
||||||
|
_columns:
|
||||||
|
main: "メイン"
|
||||||
|
widgets: "ウィジェット"
|
||||||
|
notifications: "通知"
|
||||||
|
tl: "タイムライン"
|
||||||
|
antenna: "アンテナ"
|
||||||
|
list: "リスト"
|
||||||
|
mentions: "あんた宛て"
|
||||||
|
direct: "ダイレクト"
|
||||||
|
_services: {}
|
|
@ -27,6 +27,9 @@ signup: "ನೋಂದಣಿ"
|
||||||
save: "ಉಳಿಸಿ"
|
save: "ಉಳಿಸಿ"
|
||||||
users: "ಬಳಕೆದಾರ"
|
users: "ಬಳಕೆದಾರ"
|
||||||
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
|
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
|
||||||
|
favorite: "ಮೆಚ್ಚಿನ"
|
||||||
|
favorites: "ಮೆಚ್ಚಿನವುಗಳು"
|
||||||
|
unfavorite: "ಮೆಚ್ಚುಗೆ ಅಳಿಸು"
|
||||||
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
|
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
|
||||||
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
|
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
|
||||||
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
|
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
|
---
|
||||||
_lang_: "한국어"
|
_lang_: "한국어"
|
||||||
headlineMisskey: "노트로 연결되는 네트워크"
|
headlineMisskey: "노트로 연결되는 네트워크"
|
||||||
introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고\
|
introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
|
||||||
\ 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요\U0001F4E1\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할\
|
|
||||||
\ 수도 있습니다\U0001F44D\n새로운 세계를 탐험해 보세요\U0001F680"
|
|
||||||
monthAndDay: "{month}월 {day}일"
|
monthAndDay: "{month}월 {day}일"
|
||||||
search: "검색"
|
search: "검색"
|
||||||
notifications: "알림"
|
notifications: "알림"
|
||||||
|
@ -13,6 +12,7 @@ fetchingAsApObject: "연합에서 조회 중"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "알겠어요"
|
gotIt: "알겠어요"
|
||||||
cancel: "취소"
|
cancel: "취소"
|
||||||
|
enterUsername: "유저명 입력"
|
||||||
renotedBy: "{user}님이 Renote"
|
renotedBy: "{user}님이 Renote"
|
||||||
noNotes: "노트가 없습니다"
|
noNotes: "노트가 없습니다"
|
||||||
noNotifications: "표시할 알림이 없습니다"
|
noNotifications: "표시할 알림이 없습니다"
|
||||||
|
@ -28,9 +28,16 @@ login: "로그인"
|
||||||
loggingIn: "로그인 중"
|
loggingIn: "로그인 중"
|
||||||
logout: "로그아웃"
|
logout: "로그아웃"
|
||||||
signup: "회원 가입"
|
signup: "회원 가입"
|
||||||
|
uploading: "업로드 중"
|
||||||
save: "저장"
|
save: "저장"
|
||||||
users: "유저"
|
users: "유저"
|
||||||
addUser: "유저 추가"
|
addUser: "유저 추가"
|
||||||
|
favorite: "즐겨찾기"
|
||||||
|
favorites: "즐겨찾기"
|
||||||
|
unfavorite: "즐겨찾기에서 제거"
|
||||||
|
favorited: "즐겨찾기에 등록했습니다"
|
||||||
|
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다"
|
||||||
|
cantFavorite: "즐겨찾기에 등록하지 못했습니다"
|
||||||
pin: "프로필에 고정"
|
pin: "프로필에 고정"
|
||||||
unpin: "프로필에서 고정 해제"
|
unpin: "프로필에서 고정 해제"
|
||||||
copyContent: "내용 복사"
|
copyContent: "내용 복사"
|
||||||
|
@ -41,6 +48,7 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니
|
||||||
addToList: "리스트에 추가"
|
addToList: "리스트에 추가"
|
||||||
sendMessage: "메시지 보내기"
|
sendMessage: "메시지 보내기"
|
||||||
copyUsername: "유저명 복사"
|
copyUsername: "유저명 복사"
|
||||||
|
searchUser: "사용자 검색"
|
||||||
reply: "답글"
|
reply: "답글"
|
||||||
loadMore: "더 보기"
|
loadMore: "더 보기"
|
||||||
showMore: "더 보기"
|
showMore: "더 보기"
|
||||||
|
@ -60,6 +68,7 @@ unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
|
||||||
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
||||||
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
||||||
lists: "리스트"
|
lists: "리스트"
|
||||||
|
noLists: "리스트가 없습니다"
|
||||||
note: "노트"
|
note: "노트"
|
||||||
notes: "노트"
|
notes: "노트"
|
||||||
following: "팔로잉"
|
following: "팔로잉"
|
||||||
|
@ -71,8 +80,7 @@ error: "오류"
|
||||||
somethingHappened: "오류가 발생했습니다"
|
somethingHappened: "오류가 발생했습니다"
|
||||||
retry: "다시 시도"
|
retry: "다시 시도"
|
||||||
pageLoadError: "페이지를 불러오지 못했습니다."
|
pageLoadError: "페이지를 불러오지 못했습니다."
|
||||||
pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후\
|
pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후 다시 시도해 주세요."
|
||||||
\ 다시 시도해 주세요."
|
|
||||||
serverIsDead: "서버로부터 응답이 없습니다. 잠시 후 다시 시도해주세요."
|
serverIsDead: "서버로부터 응답이 없습니다. 잠시 후 다시 시도해주세요."
|
||||||
youShouldUpgradeClient: "이 페이지를 표시하려면 새로고침하여 새로운 버전의 클라이언트를 이용해 주십시오."
|
youShouldUpgradeClient: "이 페이지를 표시하려면 새로고침하여 새로운 버전의 클라이언트를 이용해 주십시오."
|
||||||
enterListName: "리스트 이름을 입력"
|
enterListName: "리스트 이름을 입력"
|
||||||
|
@ -84,15 +92,21 @@ followRequest: "팔로우 요청"
|
||||||
followRequests: "팔로우 요청"
|
followRequests: "팔로우 요청"
|
||||||
unfollow: "팔로우 해제"
|
unfollow: "팔로우 해제"
|
||||||
followRequestPending: "팔로우 허가 대기중"
|
followRequestPending: "팔로우 허가 대기중"
|
||||||
|
enterEmoji: "이모지 입력"
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
unrenote: "Renote 취소"
|
unrenote: "Renote 취소"
|
||||||
|
renoted: "Renote 하였습니다"
|
||||||
|
cantRenote: "이 게시물은 Renote할 수 없습니다."
|
||||||
|
cantReRenote: "Renote를 Renote할 수 없습니다."
|
||||||
quote: "인용"
|
quote: "인용"
|
||||||
pinnedNote: "고정해놓은 노트"
|
pinnedNote: "고정해놓은 노트"
|
||||||
|
pinned: "프로필에 고정"
|
||||||
you: "당신"
|
you: "당신"
|
||||||
clickToShow: "클릭하여 보기"
|
clickToShow: "클릭하여 보기"
|
||||||
sensitive: "열람주의"
|
sensitive: "열람주의"
|
||||||
add: "추가"
|
add: "추가"
|
||||||
reaction: "리액션"
|
reaction: "리액션"
|
||||||
|
reactionSetting: "선택기에 표시할 리액션"
|
||||||
reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
|
reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
|
||||||
attachCancel: "첨부 취소"
|
attachCancel: "첨부 취소"
|
||||||
markAsSensitive: "열람주의로 설정"
|
markAsSensitive: "열람주의로 설정"
|
||||||
|
@ -116,13 +130,14 @@ editWidgetsExit: "편집 종료"
|
||||||
customEmojis: "커스텀 이모지"
|
customEmojis: "커스텀 이모지"
|
||||||
emoji: "이모지"
|
emoji: "이모지"
|
||||||
emojis: "이모지"
|
emojis: "이모지"
|
||||||
|
emojiName: "이모지 이름"
|
||||||
|
emojiUrl: "이모지 URL"
|
||||||
addEmoji: "이모지 추가"
|
addEmoji: "이모지 추가"
|
||||||
|
settingGuide: "추천 설정"
|
||||||
cacheRemoteFiles: "리모트 파일을 캐시"
|
cacheRemoteFiles: "리모트 파일을 캐시"
|
||||||
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라\
|
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
||||||
\ 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
|
||||||
flagAsBot: "나는 봇입니다"
|
flagAsBot: "나는 봇입니다"
|
||||||
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여\
|
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
||||||
\ 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
|
||||||
flagAsCat: "나는 고양이다냥"
|
flagAsCat: "나는 고양이다냥"
|
||||||
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
|
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
|
||||||
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
|
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
|
||||||
|
@ -132,32 +147,40 @@ addAccount: "계정 추가"
|
||||||
loginFailed: "로그인에 실패했습니다"
|
loginFailed: "로그인에 실패했습니다"
|
||||||
showOnRemote: "리모트에서 보기"
|
showOnRemote: "리모트에서 보기"
|
||||||
general: "일반"
|
general: "일반"
|
||||||
|
wallpaper: "배경"
|
||||||
setWallpaper: "배경화면 설정"
|
setWallpaper: "배경화면 설정"
|
||||||
removeWallpaper: "배경 제거"
|
removeWallpaper: "배경 제거"
|
||||||
|
searchWith: "검색: {q}"
|
||||||
youHaveNoLists: "리스트가 없습니다"
|
youHaveNoLists: "리스트가 없습니다"
|
||||||
followConfirm: "{name}님을 팔로우 하시겠습니까?"
|
followConfirm: "{name}님을 팔로우 하시겠습니까?"
|
||||||
proxyAccount: "프록시 계정"
|
proxyAccount: "프록시 계정"
|
||||||
proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트\
|
proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다."
|
||||||
\ 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이\
|
|
||||||
\ 해당 유저를 팔로우하도록 합니다."
|
|
||||||
host: "호스트"
|
host: "호스트"
|
||||||
selectUser: "유저 선택"
|
selectUser: "유저 선택"
|
||||||
recipient: "수신인"
|
recipient: "수신인"
|
||||||
annotation: "내용에 대한 주석"
|
annotation: "내용에 대한 주석"
|
||||||
federation: "연합"
|
federation: "연합"
|
||||||
|
instances: "인스턴스"
|
||||||
registeredAt: "등록 날짜"
|
registeredAt: "등록 날짜"
|
||||||
latestRequestSentAt: "마지막으로 요청을 보낸 시간"
|
latestRequestSentAt: "마지막으로 요청을 보낸 시간"
|
||||||
latestRequestReceivedAt: "마지막으로 요청을 받은 시간"
|
latestRequestReceivedAt: "마지막으로 요청을 받은 시간"
|
||||||
latestStatus: "마지막 상태"
|
latestStatus: "마지막 상태"
|
||||||
|
storageUsage: "스토리지 사용량"
|
||||||
charts: "차트"
|
charts: "차트"
|
||||||
perHour: "1시간마다"
|
perHour: "1시간마다"
|
||||||
perDay: "1일마다"
|
perDay: "1일마다"
|
||||||
stopActivityDelivery: "액티비티 보내지 않기"
|
stopActivityDelivery: "액티비티 보내지 않기"
|
||||||
blockThisInstance: "이 인스턴스를 차단"
|
blockThisInstance: "이 인스턴스를 차단"
|
||||||
|
operations: "작업"
|
||||||
software: "소프트웨어"
|
software: "소프트웨어"
|
||||||
version: "버전"
|
version: "버전"
|
||||||
|
metadata: "메타데이터"
|
||||||
withNFiles: "{n}개의 파일"
|
withNFiles: "{n}개의 파일"
|
||||||
|
monitor: "모니터"
|
||||||
jobQueue: "작업 대기열"
|
jobQueue: "작업 대기열"
|
||||||
|
cpuAndMemory: "CPU와 메모리"
|
||||||
|
network: "네트워크"
|
||||||
|
disk: "디스크"
|
||||||
instanceInfo: "인스턴스 정보"
|
instanceInfo: "인스턴스 정보"
|
||||||
statistics: "통계"
|
statistics: "통계"
|
||||||
clearQueue: "대기열 비우기"
|
clearQueue: "대기열 비우기"
|
||||||
|
@ -166,8 +189,7 @@ clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되
|
||||||
clearCachedFiles: "캐시 비우기"
|
clearCachedFiles: "캐시 비우기"
|
||||||
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
|
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
|
||||||
blockedInstances: "차단된 인스턴스"
|
blockedInstances: "차단된 인스턴스"
|
||||||
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와\
|
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
|
||||||
\ 통신할 수 없게 됩니다."
|
|
||||||
muteAndBlock: "뮤트 및 차단"
|
muteAndBlock: "뮤트 및 차단"
|
||||||
mutedUsers: "뮤트한 유저"
|
mutedUsers: "뮤트한 유저"
|
||||||
blockedUsers: "차단한 유저"
|
blockedUsers: "차단한 유저"
|
||||||
|
@ -189,6 +211,9 @@ all: "전체"
|
||||||
subscribing: "구독 중"
|
subscribing: "구독 중"
|
||||||
publishing: "배포 중"
|
publishing: "배포 중"
|
||||||
notResponding: "응답 없음"
|
notResponding: "응답 없음"
|
||||||
|
instanceFollowing: "인스턴스의 팔로잉"
|
||||||
|
instanceFollowers: "인스턴스의 팔로워"
|
||||||
|
instanceUsers: "인스턴스의 유저"
|
||||||
changePassword: "비밀번호 변경"
|
changePassword: "비밀번호 변경"
|
||||||
security: "보안"
|
security: "보안"
|
||||||
retypedNotMatch: "입력이 일치하지 않습니다."
|
retypedNotMatch: "입력이 일치하지 않습니다."
|
||||||
|
@ -204,6 +229,7 @@ lookup: "조회"
|
||||||
announcements: "공지사항"
|
announcements: "공지사항"
|
||||||
imageUrl: "이미지 URL"
|
imageUrl: "이미지 URL"
|
||||||
remove: "삭제"
|
remove: "삭제"
|
||||||
|
removed: "삭제하였습니다"
|
||||||
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||||
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||||
resetAreYouSure: "초기화 하시겠습니까?"
|
resetAreYouSure: "초기화 하시겠습니까?"
|
||||||
|
@ -211,14 +237,14 @@ saved: "저장하였습니다"
|
||||||
messaging: "대화"
|
messaging: "대화"
|
||||||
upload: "업로드"
|
upload: "업로드"
|
||||||
keepOriginalUploading: "원본 이미지를 유지"
|
keepOriginalUploading: "원본 이미지를 유지"
|
||||||
keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서\
|
keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서 웹 공개용 이미지를 생성합니다."
|
||||||
\ 웹 공개용 이미지를 생성합니다."
|
|
||||||
fromDrive: "드라이브에서"
|
fromDrive: "드라이브에서"
|
||||||
fromUrl: "URL로부터"
|
fromUrl: "URL로부터"
|
||||||
uploadFromUrl: "URL 업로드"
|
uploadFromUrl: "URL 업로드"
|
||||||
uploadFromUrlDescription: "업로드하려는 파일의 URL"
|
uploadFromUrlDescription: "업로드하려는 파일의 URL"
|
||||||
uploadFromUrlRequested: "업로드를 요청했습니다"
|
uploadFromUrlRequested: "업로드를 요청했습니다"
|
||||||
uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다."
|
uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다."
|
||||||
|
explore: "발견하기"
|
||||||
messageRead: "읽음"
|
messageRead: "읽음"
|
||||||
noMoreHistory: "이것보다 과거의 기록이 없습니다"
|
noMoreHistory: "이것보다 과거의 기록이 없습니다"
|
||||||
startMessaging: "대화 시작하기"
|
startMessaging: "대화 시작하기"
|
||||||
|
@ -243,6 +269,7 @@ lightThemes: "밝은 테마"
|
||||||
darkThemes: "어두운 테마"
|
darkThemes: "어두운 테마"
|
||||||
syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화"
|
syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화"
|
||||||
drive: "드라이브"
|
drive: "드라이브"
|
||||||
|
fileName: "파일명"
|
||||||
selectFile: "파일 선택"
|
selectFile: "파일 선택"
|
||||||
selectFiles: "파일 선택"
|
selectFiles: "파일 선택"
|
||||||
selectFolder: "폴더 선택"
|
selectFolder: "폴더 선택"
|
||||||
|
@ -253,6 +280,8 @@ createFolder: "폴더 만들기"
|
||||||
renameFolder: "폴더 이름 바꾸기"
|
renameFolder: "폴더 이름 바꾸기"
|
||||||
deleteFolder: "폴더 삭제"
|
deleteFolder: "폴더 삭제"
|
||||||
addFile: "파일 추가"
|
addFile: "파일 추가"
|
||||||
|
emptyDrive: "드라이브가 비어 있습니다"
|
||||||
|
emptyFolder: "폴더가 비어 있습니다"
|
||||||
unableToDelete: "삭제할 수 없습니다"
|
unableToDelete: "삭제할 수 없습니다"
|
||||||
inputNewFileName: "바꿀 파일명을 입력해 주세요"
|
inputNewFileName: "바꿀 파일명을 입력해 주세요"
|
||||||
inputNewDescription: "새 캡션을 입력해 주세요"
|
inputNewDescription: "새 캡션을 입력해 주세요"
|
||||||
|
@ -289,6 +318,7 @@ pages: "페이지"
|
||||||
enableLocalTimeline: "로컬 타임라인 활성화"
|
enableLocalTimeline: "로컬 타임라인 활성화"
|
||||||
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
||||||
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
||||||
|
registration: "등록"
|
||||||
enableRegistration: "신규 회원가입을 활성화"
|
enableRegistration: "신규 회원가입을 활성화"
|
||||||
invite: "초대"
|
invite: "초대"
|
||||||
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
||||||
|
@ -297,10 +327,22 @@ inMb: "메가바이트 단위"
|
||||||
iconUrl: "아이콘 URL"
|
iconUrl: "아이콘 URL"
|
||||||
bannerUrl: "배너 이미지 URL"
|
bannerUrl: "배너 이미지 URL"
|
||||||
backgroundImageUrl: "배경 이미지 URL"
|
backgroundImageUrl: "배경 이미지 URL"
|
||||||
|
basicInfo: "기본 정보"
|
||||||
|
pinnedUsers: "고정된 유저"
|
||||||
|
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
|
||||||
|
pinnedPages: "고정한 페이지"
|
||||||
|
pinnedPagesDescription: "인스턴스의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
|
||||||
|
pinnedClipId: "고정할 클립의 ID"
|
||||||
|
pinnedNotes: "고정해놓은 노트"
|
||||||
|
hcaptcha: "hCaptcha"
|
||||||
|
enableHcaptcha: "hCaptcha 활성화"
|
||||||
hcaptchaSiteKey: "사이트 키"
|
hcaptchaSiteKey: "사이트 키"
|
||||||
hcaptchaSecretKey: "시크릿 키"
|
hcaptchaSecretKey: "시크릿 키"
|
||||||
|
recaptcha: "reCAPTCHA"
|
||||||
|
enableRecaptcha: "reCAPTCHA 활성화"
|
||||||
recaptchaSiteKey: "사이트 키"
|
recaptchaSiteKey: "사이트 키"
|
||||||
recaptchaSecretKey: "시크릿 키"
|
recaptchaSecretKey: "시크릿 키"
|
||||||
|
avoidMultiCaptchaConfirm: "여러 Captcha를 사용하는 경우 간섭이 발생할 가능성이 있습니다. 다른 Captcha를 비활성화하시겠습니까? 취소를 눌러 여러 Captcha를 활성화한 상태로 두는 것도 가능합니다."
|
||||||
antennas: "안테나"
|
antennas: "안테나"
|
||||||
manageAntennas: "안테나 관리"
|
manageAntennas: "안테나 관리"
|
||||||
name: "이름"
|
name: "이름"
|
||||||
|
@ -310,6 +352,7 @@ antennaExcludeKeywords: "제외할 키워드"
|
||||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
||||||
notifyAntenna: "새로운 노트를 알림"
|
notifyAntenna: "새로운 노트를 알림"
|
||||||
withFileAntenna: "파일이 첨부된 노트만"
|
withFileAntenna: "파일이 첨부된 노트만"
|
||||||
|
enableServiceworker: "ServiceWorker 사용"
|
||||||
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
|
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
|
||||||
caseSensitive: "대소문자를 구분"
|
caseSensitive: "대소문자를 구분"
|
||||||
withReplies: "답글 포함"
|
withReplies: "답글 포함"
|
||||||
|
@ -320,7 +363,15 @@ silence: "사일런스"
|
||||||
silenceConfirm: "이 계정을 사일런스로 설정하시겠습니까?"
|
silenceConfirm: "이 계정을 사일런스로 설정하시겠습니까?"
|
||||||
unsilence: "사일런스 해제"
|
unsilence: "사일런스 해제"
|
||||||
unsilenceConfirm: "이 계정의 사일런스를 해제하시겠습니까?"
|
unsilenceConfirm: "이 계정의 사일런스를 해제하시겠습니까?"
|
||||||
|
popularUsers: "인기 유저"
|
||||||
|
recentlyUpdatedUsers: "최근 활동한 유저"
|
||||||
|
recentlyRegisteredUsers: "최근 가입한 유저"
|
||||||
|
recentlyDiscoveredUsers: "최근 발견한 유저"
|
||||||
|
exploreUsersCount: "{count}명의 유저가 있습니다"
|
||||||
|
exploreFediverse: "연합우주를 탐색"
|
||||||
|
popularTags: "인기 태그"
|
||||||
userList: "리스트"
|
userList: "리스트"
|
||||||
|
about: "정보"
|
||||||
aboutMisskey: "FoundKey에 대하여"
|
aboutMisskey: "FoundKey에 대하여"
|
||||||
administrator: "관리자"
|
administrator: "관리자"
|
||||||
token: "토큰"
|
token: "토큰"
|
||||||
|
@ -340,6 +391,7 @@ share: "공유"
|
||||||
notFound: "찾을 수 없습니다"
|
notFound: "찾을 수 없습니다"
|
||||||
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
|
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
|
||||||
uploadFolder: "기본 업로드 위치"
|
uploadFolder: "기본 업로드 위치"
|
||||||
|
cacheClear: "캐시 지우기"
|
||||||
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
|
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
|
||||||
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
|
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
|
||||||
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
|
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
|
||||||
|
@ -360,6 +412,7 @@ messagingWithGroup: "그룹끼리 대화하기"
|
||||||
title: "제목"
|
title: "제목"
|
||||||
text: "텍스트"
|
text: "텍스트"
|
||||||
enable: "사용"
|
enable: "사용"
|
||||||
|
next: "다음"
|
||||||
retype: "다시 입력"
|
retype: "다시 입력"
|
||||||
noteOf: "{user}의 노트"
|
noteOf: "{user}의 노트"
|
||||||
inviteToGroup: "그룹에 초대하기"
|
inviteToGroup: "그룹에 초대하기"
|
||||||
|
@ -369,6 +422,7 @@ noMessagesYet: "아직 대화가 없습니다"
|
||||||
newMessageExists: "새 메시지가 있습니다"
|
newMessageExists: "새 메시지가 있습니다"
|
||||||
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||||
signinRequired: "로그인 해주세요"
|
signinRequired: "로그인 해주세요"
|
||||||
|
invitations: "초대"
|
||||||
invitationCode: "초대 코드"
|
invitationCode: "초대 코드"
|
||||||
checking: "확인하는 중입니다"
|
checking: "확인하는 중입니다"
|
||||||
available: "사용 가능합니다"
|
available: "사용 가능합니다"
|
||||||
|
@ -387,6 +441,7 @@ or: "혹은"
|
||||||
language: "언어"
|
language: "언어"
|
||||||
uiLanguage: "UI 표시 언어"
|
uiLanguage: "UI 표시 언어"
|
||||||
groupInvited: "그룹에 초대되었습니다"
|
groupInvited: "그룹에 초대되었습니다"
|
||||||
|
aboutX: "{x}에 대하여"
|
||||||
useOsNativeEmojis: "OS 기본 이모지를 사용"
|
useOsNativeEmojis: "OS 기본 이모지를 사용"
|
||||||
disableDrawer: "드로어 메뉴를 사용하지 않기"
|
disableDrawer: "드로어 메뉴를 사용하지 않기"
|
||||||
youHaveNoGroups: "그룹이 없습니다"
|
youHaveNoGroups: "그룹이 없습니다"
|
||||||
|
@ -394,42 +449,47 @@ joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을
|
||||||
noHistory: "기록이 없습니다"
|
noHistory: "기록이 없습니다"
|
||||||
signinHistory: "로그인 기록"
|
signinHistory: "로그인 기록"
|
||||||
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
|
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
|
||||||
|
doing: "잠시만요"
|
||||||
category: "카테고리"
|
category: "카테고리"
|
||||||
tags: "태그"
|
tags: "태그"
|
||||||
|
docSource: "이 문서의 소스"
|
||||||
createAccount: "계정 만들기"
|
createAccount: "계정 만들기"
|
||||||
existingAccount: "기존 계정"
|
existingAccount: "기존 계정"
|
||||||
|
regenerate: "재생성"
|
||||||
fontSize: "글자 크기"
|
fontSize: "글자 크기"
|
||||||
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
||||||
openImageInNewTab: "새 탭에서 이미지 열기"
|
openImageInNewTab: "새 탭에서 이미지 열기"
|
||||||
dashboard: "대시보드"
|
dashboard: "대시보드"
|
||||||
local: "로컬"
|
local: "로컬"
|
||||||
remote: "리모트"
|
remote: "리모트"
|
||||||
|
total: "합계"
|
||||||
|
weekOverWeekChanges: "지난주보다"
|
||||||
dayOverDayChanges: "어제보다"
|
dayOverDayChanges: "어제보다"
|
||||||
appearance: "모양"
|
appearance: "모양"
|
||||||
clientSettings: "클라이언트 설정"
|
clientSettings: "클라이언트 설정"
|
||||||
|
accountSettings: "계정 설정"
|
||||||
|
numberOfDays: "며칠동안"
|
||||||
|
hideThisNote: "이 노트를 숨기기"
|
||||||
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
|
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
|
||||||
objectStorage: "오브젝트 스토리지"
|
objectStorage: "오브젝트 스토리지"
|
||||||
useObjectStorage: "오브젝트 스토리지를 사용"
|
useObjectStorage: "오브젝트 스토리지를 사용"
|
||||||
objectStorageBaseUrl: "Base URL"
|
objectStorageBaseUrl: "Base URL"
|
||||||
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는\
|
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어, AWS S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>' 와 같이 지정합니다."
|
||||||
\ 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어,\
|
|
||||||
\ AWS S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>'\
|
|
||||||
\ 와 같이 지정합니다."
|
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "사용 서비스의 bucket명을 지정해주세요."
|
objectStorageBucketDesc: "사용 서비스의 bucket명을 지정해주세요."
|
||||||
objectStoragePrefix: "Prefix"
|
objectStoragePrefix: "Prefix"
|
||||||
objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장됩니다."
|
objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장됩니다."
|
||||||
objectStorageEndpoint: "Endpoint"
|
objectStorageEndpoint: "Endpoint"
|
||||||
objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요.\
|
objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요. '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
|
||||||
\ '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
|
|
||||||
objectStorageRegion: "Region"
|
objectStorageRegion: "Region"
|
||||||
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는\
|
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
|
||||||
\ 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
|
|
||||||
objectStorageUseSSL: "SSL 사용"
|
objectStorageUseSSL: "SSL 사용"
|
||||||
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||||
objectStorageUseProxy: "연결에 프록시를 사용"
|
objectStorageUseProxy: "연결에 프록시를 사용"
|
||||||
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||||
objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
||||||
|
serverLogs: "서버 로그"
|
||||||
|
deleteAll: "모두 삭제"
|
||||||
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
||||||
newNoteRecived: "새 노트가 있습니다"
|
newNoteRecived: "새 노트가 있습니다"
|
||||||
sounds: "소리"
|
sounds: "소리"
|
||||||
|
@ -440,6 +500,7 @@ popout: "새 창으로 열기"
|
||||||
volume: "음량"
|
volume: "음량"
|
||||||
masterVolume: "마스터 볼륨"
|
masterVolume: "마스터 볼륨"
|
||||||
details: "자세히"
|
details: "자세히"
|
||||||
|
chooseEmoji: "이모지 선택"
|
||||||
unableToProcess: "작업을 완료할 수 없습니다"
|
unableToProcess: "작업을 완료할 수 없습니다"
|
||||||
recentUsed: "최근 사용"
|
recentUsed: "최근 사용"
|
||||||
install: "설치"
|
install: "설치"
|
||||||
|
@ -453,27 +514,28 @@ sort: "정렬"
|
||||||
ascendingOrder: "오름차순"
|
ascendingOrder: "오름차순"
|
||||||
descendingOrder: "내림차순"
|
descendingOrder: "내림차순"
|
||||||
scratchpad: "스크래치 패드"
|
scratchpad: "스크래치 패드"
|
||||||
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를\
|
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다."
|
||||||
\ 작성, 실행 및 결과를 확인할 수 있습니다."
|
|
||||||
output: "출력"
|
output: "출력"
|
||||||
|
script: "스크립트"
|
||||||
updateRemoteUser: "리모트 유저 정보 갱신"
|
updateRemoteUser: "리모트 유저 정보 갱신"
|
||||||
deleteAllFiles: "모든 파일 삭제"
|
deleteAllFiles: "모든 파일 삭제"
|
||||||
deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?"
|
deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?"
|
||||||
removeAllFollowing: "모든 팔로잉 해제"
|
removeAllFollowing: "모든 팔로잉 해제"
|
||||||
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게\
|
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요."
|
||||||
\ 된 경우 등에 실행해 주세요."
|
|
||||||
userSuspended: "이 계정은 정지된 상태입니다."
|
userSuspended: "이 계정은 정지된 상태입니다."
|
||||||
userSilenced: "이 계정은 사일런스된 상태입니다."
|
userSilenced: "이 계정은 사일런스된 상태입니다."
|
||||||
yourAccountSuspendedTitle: "계정이 정지되었습니다"
|
yourAccountSuspendedTitle: "계정이 정지되었습니다"
|
||||||
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한\
|
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
|
||||||
\ 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
|
|
||||||
menu: "메뉴"
|
menu: "메뉴"
|
||||||
divider: "구분선"
|
divider: "구분선"
|
||||||
addItem: "항목 추가"
|
addItem: "항목 추가"
|
||||||
relays: "릴레이"
|
relays: "릴레이"
|
||||||
addRelay: "릴레이 추가"
|
addRelay: "릴레이 추가"
|
||||||
inboxUrl: "Inbox 주소"
|
inboxUrl: "Inbox 주소"
|
||||||
|
addedRelays: "추가된 릴레이"
|
||||||
|
serviceworkerInfo: "푸시 알림을 수행하려면 활성화해야 합니다."
|
||||||
deletedNote: "삭제된 노트"
|
deletedNote: "삭제된 노트"
|
||||||
|
invisibleNote: "비공개 노트"
|
||||||
enableInfiniteScroll: "자동으로 좀 더 보기"
|
enableInfiniteScroll: "자동으로 좀 더 보기"
|
||||||
visibility: "공개 범위"
|
visibility: "공개 범위"
|
||||||
poll: "투표"
|
poll: "투표"
|
||||||
|
@ -483,12 +545,15 @@ disablePlayer: "플레이어 닫기"
|
||||||
themeEditor: "테마 에디터"
|
themeEditor: "테마 에디터"
|
||||||
description: "설명"
|
description: "설명"
|
||||||
describeFile: "캡션 추가"
|
describeFile: "캡션 추가"
|
||||||
|
enterFileDescription: "캡션 입력"
|
||||||
author: "작성자"
|
author: "작성자"
|
||||||
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
|
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
|
||||||
manage: "관리"
|
manage: "관리"
|
||||||
plugins: "플러그인"
|
plugins: "플러그인"
|
||||||
deck: "덱"
|
deck: "덱"
|
||||||
|
undeck: "덱 해제"
|
||||||
useBlurEffectForModal: "모달에 흐림 효과 사용"
|
useBlurEffectForModal: "모달에 흐림 효과 사용"
|
||||||
|
useFullReactionPicker: "모든 기능이 포함된 리액션 선택기 사용"
|
||||||
width: "폭"
|
width: "폭"
|
||||||
height: "높이"
|
height: "높이"
|
||||||
large: "크게"
|
large: "크게"
|
||||||
|
@ -500,6 +565,7 @@ enableAll: "전체 선택"
|
||||||
disableAll: "전체 해제"
|
disableAll: "전체 해제"
|
||||||
tokenRequested: "계정 접근 허용"
|
tokenRequested: "계정 접근 허용"
|
||||||
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
|
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
|
||||||
|
notificationType: "알림 유형"
|
||||||
edit: "편집"
|
edit: "편집"
|
||||||
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
||||||
emailServer: "메일 서버"
|
emailServer: "메일 서버"
|
||||||
|
@ -524,7 +590,10 @@ userSaysSomething: "{name}님이 무언가를 말했습니다"
|
||||||
makeActive: "활성화"
|
makeActive: "활성화"
|
||||||
display: "표시"
|
display: "표시"
|
||||||
copy: "복사"
|
copy: "복사"
|
||||||
|
metrics: "통계"
|
||||||
overview: "요약"
|
overview: "요약"
|
||||||
|
logs: "로그"
|
||||||
|
delayed: "지연"
|
||||||
database: "데이터베이스"
|
database: "데이터베이스"
|
||||||
channel: "채널"
|
channel: "채널"
|
||||||
create: "생성"
|
create: "생성"
|
||||||
|
@ -534,15 +603,16 @@ useGlobalSetting: "글로벌 설정을 사용하기"
|
||||||
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
|
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
|
||||||
other: "기타"
|
other: "기타"
|
||||||
regenerateLoginToken: "로그인 토큰을 재생성"
|
regenerateLoginToken: "로그인 토큰을 재생성"
|
||||||
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다.\
|
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
||||||
\ 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
|
||||||
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||||
fileIdOrUrl: "파일 ID 또는 URL"
|
fileIdOrUrl: "파일 ID 또는 URL"
|
||||||
behavior: "동작"
|
behavior: "동작"
|
||||||
|
sample: "예시"
|
||||||
abuseReports: "신고"
|
abuseReports: "신고"
|
||||||
reportAbuse: "신고"
|
reportAbuse: "신고"
|
||||||
reportAbuseOf: "{name}을 신고하기"
|
reportAbuseOf: "{name}을 신고하기"
|
||||||
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요."
|
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요."
|
||||||
|
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
|
||||||
reporter: "신고자"
|
reporter: "신고자"
|
||||||
reporteeOrigin: "피신고자"
|
reporteeOrigin: "피신고자"
|
||||||
reporterOrigin: "신고자"
|
reporterOrigin: "신고자"
|
||||||
|
@ -551,8 +621,12 @@ forwardReportIsAnonymous: "리모트 인스턴스에서는 나의 정보를 볼
|
||||||
send: "전송"
|
send: "전송"
|
||||||
abuseMarkAsResolved: "해결됨으로 표시"
|
abuseMarkAsResolved: "해결됨으로 표시"
|
||||||
openInNewTab: "새 탭에서 열기"
|
openInNewTab: "새 탭에서 열기"
|
||||||
|
openInSideView: "사이드뷰로 열기"
|
||||||
defaultNavigationBehaviour: "기본 탐색 동작"
|
defaultNavigationBehaviour: "기본 탐색 동작"
|
||||||
|
editTheseSettingsMayBreakAccount: "이 설정을 변경하면 계정이 손상될 수 있습니다."
|
||||||
instanceTicker: "노트의 인스턴스 정보"
|
instanceTicker: "노트의 인스턴스 정보"
|
||||||
|
waitingFor: "{x}을(를) 기다리고 있습니다"
|
||||||
|
random: "랜덤"
|
||||||
system: "시스템"
|
system: "시스템"
|
||||||
switchUi: "UI 전환"
|
switchUi: "UI 전환"
|
||||||
desktop: "데스크탑"
|
desktop: "데스크탑"
|
||||||
|
@ -586,12 +660,16 @@ alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
|
||||||
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
|
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
|
||||||
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
|
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
|
||||||
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
|
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
|
||||||
|
notSet: "설정되지 않음"
|
||||||
emailVerified: "메일 주소가 확인되었습니다."
|
emailVerified: "메일 주소가 확인되었습니다."
|
||||||
|
noteFavoritesCount: "즐겨찾기한 노트 수"
|
||||||
pageLikesCount: "좋아요 한 Page 수"
|
pageLikesCount: "좋아요 한 Page 수"
|
||||||
pageLikedCount: "Page에 받은 좋아요 수"
|
pageLikedCount: "Page에 받은 좋아요 수"
|
||||||
contact: "연락처"
|
contact: "연락처"
|
||||||
useSystemFont: "시스템 기본 글꼴을 사용"
|
useSystemFont: "시스템 기본 글꼴을 사용"
|
||||||
clips: "클립"
|
clips: "클립"
|
||||||
|
experimentalFeatures: "실험실"
|
||||||
|
developer: "개발자"
|
||||||
makeExplorable: "\"발견하기\"에 내 계정 보이기"
|
makeExplorable: "\"발견하기\"에 내 계정 보이기"
|
||||||
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
|
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
|
||||||
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
|
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
|
||||||
|
@ -602,16 +680,28 @@ wide: "넓게"
|
||||||
narrow: "좁게"
|
narrow: "좁게"
|
||||||
reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
|
reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
|
||||||
needReloadToApply: "변경 사항은 새로고침하면 적용됩니다."
|
needReloadToApply: "변경 사항은 새로고침하면 적용됩니다."
|
||||||
|
showTitlebar: "타이틀 바를 표시하기"
|
||||||
clearCache: "캐시 비우기"
|
clearCache: "캐시 비우기"
|
||||||
onlineUsersCount: "{n}명이 접속 중"
|
onlineUsersCount: "{n}명이 접속 중"
|
||||||
|
nUsers: "{n} 유저"
|
||||||
|
nNotes: "{n} 노트"
|
||||||
|
myTheme: "내 테마"
|
||||||
backgroundColor: "배경 색"
|
backgroundColor: "배경 색"
|
||||||
accentColor: "강조 색상"
|
accentColor: "강조 색상"
|
||||||
textColor: "문자 색"
|
textColor: "문자 색"
|
||||||
saveAs: "다른 이름으로 저장"
|
saveAs: "다른 이름으로 저장"
|
||||||
|
advanced: "고급"
|
||||||
|
value: "값"
|
||||||
createdAt: "생성된 날짜"
|
createdAt: "생성된 날짜"
|
||||||
updatedAt: "수정한 날짜"
|
updatedAt: "수정한 날짜"
|
||||||
|
saveConfirm: "저장하시겠습니까?"
|
||||||
deleteConfirm: "삭제하시겠습니까?"
|
deleteConfirm: "삭제하시겠습니까?"
|
||||||
|
invalidValue: "올바른 값이 아닙니다."
|
||||||
|
registry: "레지스트리"
|
||||||
closeAccount: "계정 폐쇄"
|
closeAccount: "계정 폐쇄"
|
||||||
|
currentVersion: "현재 버전"
|
||||||
|
latestVersion: "최신 버전"
|
||||||
|
youAreRunningUpToDateClient: "사용 중인 클라이언트는 최신입니다."
|
||||||
newVersionOfClientAvailable: "새로운 버전의 클라이언트를 이용할 수 있습니다."
|
newVersionOfClientAvailable: "새로운 버전의 클라이언트를 이용할 수 있습니다."
|
||||||
usageAmount: "사용량"
|
usageAmount: "사용량"
|
||||||
capacity: "용량"
|
capacity: "용량"
|
||||||
|
@ -620,9 +710,12 @@ editCode: "코드 수정"
|
||||||
apply: "적용"
|
apply: "적용"
|
||||||
receiveAnnouncementFromInstance: "이 인스턴스의 알림을 이메일로 수신할게요"
|
receiveAnnouncementFromInstance: "이 인스턴스의 알림을 이메일로 수신할게요"
|
||||||
emailNotification: "메일 알림"
|
emailNotification: "메일 알림"
|
||||||
|
publish: "게시"
|
||||||
|
inChannelSearch: "채널에서 검색"
|
||||||
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
||||||
typingUsers: "{users} 님이 입력하고 있어요.."
|
typingUsers: "{users} 님이 입력하고 있어요.."
|
||||||
jumpToSpecifiedDate: "특정 날짜로 이동"
|
jumpToSpecifiedDate: "특정 날짜로 이동"
|
||||||
|
showingPastTimeline: "과거의 타임라인을 표시하고 있어요"
|
||||||
clear: "지우기"
|
clear: "지우기"
|
||||||
markAllAsRead: "모두 읽은 상태로 표시"
|
markAllAsRead: "모두 읽은 상태로 표시"
|
||||||
goBack: "뒤로"
|
goBack: "뒤로"
|
||||||
|
@ -635,6 +728,7 @@ notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어
|
||||||
info: "정보"
|
info: "정보"
|
||||||
userInfo: "유저 정보"
|
userInfo: "유저 정보"
|
||||||
unknown: "알 수 없음"
|
unknown: "알 수 없음"
|
||||||
|
onlineStatus: "온라인 상태"
|
||||||
hideOnlineStatus: "온라인 상태 숨기기"
|
hideOnlineStatus: "온라인 상태 숨기기"
|
||||||
hideOnlineStatusDescription: "온라인 상태를 숨기면, 검색과 같은 일부 기능에 영향을 미칠 수 있습니다."
|
hideOnlineStatusDescription: "온라인 상태를 숨기면, 검색과 같은 일부 기능에 영향을 미칠 수 있습니다."
|
||||||
online: "온라인"
|
online: "온라인"
|
||||||
|
@ -655,15 +749,26 @@ switch: "전환"
|
||||||
noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습니다."
|
noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습니다."
|
||||||
noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
|
noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
|
||||||
configure: "설정하기"
|
configure: "설정하기"
|
||||||
|
postToGallery: "갤러리에 업로드"
|
||||||
|
gallery: "갤러리"
|
||||||
recentPosts: "최근 포스트"
|
recentPosts: "최근 포스트"
|
||||||
|
popularPosts: "인기 포스트"
|
||||||
shareWithNote: "노트로 공유"
|
shareWithNote: "노트로 공유"
|
||||||
|
expiration: "기한"
|
||||||
|
memo: "메모"
|
||||||
|
priority: "우선순위"
|
||||||
|
high: "높음"
|
||||||
|
middle: "보통"
|
||||||
|
low: "낮음"
|
||||||
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
|
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
|
||||||
ratio: "비율"
|
ratio: "비율"
|
||||||
previewNoteText: "본문 미리보기"
|
previewNoteText: "본문 미리보기"
|
||||||
customCss: "CSS 사용자화"
|
customCss: "CSS 사용자화"
|
||||||
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수\
|
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다."
|
||||||
\ 있습니다."
|
global: "글로벌"
|
||||||
squareAvatars: "프로필 아이콘을 사각형으로 표시"
|
squareAvatars: "프로필 아이콘을 사각형으로 표시"
|
||||||
|
sent: "전송"
|
||||||
|
received: "수신"
|
||||||
searchResult: "검색 결과"
|
searchResult: "검색 결과"
|
||||||
hashtags: "해시태그"
|
hashtags: "해시태그"
|
||||||
troubleshooting: "문제 해결"
|
troubleshooting: "문제 해결"
|
||||||
|
@ -674,8 +779,7 @@ whatIsNew: "패치 정보 보기"
|
||||||
translate: "번역"
|
translate: "번역"
|
||||||
translatedFrom: "{x}에서 번역"
|
translatedFrom: "{x}에서 번역"
|
||||||
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
|
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
|
||||||
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다.\
|
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
|
||||||
\ 사용자명은 나중에 변경할 수 없습니다."
|
|
||||||
keepCw: "CW 유지하기"
|
keepCw: "CW 유지하기"
|
||||||
pubSub: "Pub/Sub 계정"
|
pubSub: "Pub/Sub 계정"
|
||||||
lastCommunication: "마지막 통신"
|
lastCommunication: "마지막 통신"
|
||||||
|
@ -739,8 +843,7 @@ _ffVisibility:
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "거의 다 끝났습니다"
|
almostThere: "거의 다 끝났습니다"
|
||||||
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
||||||
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해\
|
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
|
||||||
\ 주세요."
|
|
||||||
_accountDelete:
|
_accountDelete:
|
||||||
accountDelete: "계정 삭제"
|
accountDelete: "계정 삭제"
|
||||||
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
|
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
|
||||||
|
@ -748,10 +851,18 @@ _accountDelete:
|
||||||
requestAccountDelete: "계정 삭제 요청"
|
requestAccountDelete: "계정 삭제 요청"
|
||||||
started: "삭제 작업이 시작되었습니다."
|
started: "삭제 작업이 시작되었습니다."
|
||||||
inProgress: "삭제 진행 중"
|
inProgress: "삭제 진행 중"
|
||||||
|
_ad:
|
||||||
|
back: "뒤로"
|
||||||
|
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
|
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
|
||||||
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
|
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
|
||||||
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
|
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
|
||||||
|
_gallery:
|
||||||
|
my: "내 갤러리"
|
||||||
|
liked: "좋아요 한 갤러리"
|
||||||
|
like: "좋아요!"
|
||||||
|
unlike: "좋아요 취소"
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "새로운 팔로워가 있습니다"
|
title: "새로운 팔로워가 있습니다"
|
||||||
|
@ -760,6 +871,7 @@ _email:
|
||||||
_plugin:
|
_plugin:
|
||||||
install: "플러그인 설치"
|
install: "플러그인 설치"
|
||||||
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
|
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
|
||||||
|
manage: "플러그인 관리"
|
||||||
_registry:
|
_registry:
|
||||||
scope: "범위"
|
scope: "범위"
|
||||||
key: "키"
|
key: "키"
|
||||||
|
@ -768,16 +880,17 @@ _registry:
|
||||||
createKey: "키 생성"
|
createKey: "키 생성"
|
||||||
_aboutMisskey:
|
_aboutMisskey:
|
||||||
about: "FoundKey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
|
about: "FoundKey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
|
||||||
|
contributors: "주요 기여자"
|
||||||
allContributors: "모든 기여자"
|
allContributors: "모든 기여자"
|
||||||
source: "소스 코드"
|
source: "소스 코드"
|
||||||
|
translation: "FoundKey를 번역하기"
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "열람주의로 설정된 미디어 숨기기"
|
respect: "열람주의로 설정된 미디어 숨기기"
|
||||||
ignore: "열람 주의 미디어 항상 표시"
|
ignore: "열람 주의 미디어 항상 표시"
|
||||||
force: "미디어 항상 숨기기"
|
force: "미디어 항상 숨기기"
|
||||||
_mfm:
|
_mfm:
|
||||||
cheatSheet: "MFM 도움말"
|
cheatSheet: "MFM 도움말"
|
||||||
intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할\
|
intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 수 있습니다."
|
||||||
\ 수 있습니다."
|
|
||||||
dummy: "FoundKey로 연합우주의 세계가 펼쳐집니다"
|
dummy: "FoundKey로 연합우주의 세계가 펼쳐집니다"
|
||||||
mention: "멘션"
|
mention: "멘션"
|
||||||
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
|
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
|
||||||
|
@ -888,6 +1001,68 @@ _theme:
|
||||||
alreadyInstalled: "이미 설치된 테마입니다"
|
alreadyInstalled: "이미 설치된 테마입니다"
|
||||||
invalid: "테마 형식이 올바르지 않습니다"
|
invalid: "테마 형식이 올바르지 않습니다"
|
||||||
make: "테마 만들기"
|
make: "테마 만들기"
|
||||||
|
base: "베이스"
|
||||||
|
addConstant: "상수 추가"
|
||||||
|
constant: "상수"
|
||||||
|
defaultValue: "기본값"
|
||||||
|
color: "색"
|
||||||
|
refProp: "프로퍼티를 참조"
|
||||||
|
refConst: "상수를 참조"
|
||||||
|
key: "키"
|
||||||
|
func: "함수"
|
||||||
|
funcKind: "함수 종류"
|
||||||
|
argument: "매개변수"
|
||||||
|
basedProp: "기준으로 할 속성 이름"
|
||||||
|
alpha: "불투명도"
|
||||||
|
darken: "어두움"
|
||||||
|
lighten: "밝음"
|
||||||
|
inputConstantName: "상수 이름을 입력하세요"
|
||||||
|
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다."
|
||||||
|
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
|
||||||
|
keys:
|
||||||
|
accent: "강조 색상"
|
||||||
|
bg: "배경"
|
||||||
|
fg: "텍스트"
|
||||||
|
focus: "포커스"
|
||||||
|
indicator: "인디케이터"
|
||||||
|
panel: "패널"
|
||||||
|
shadow: "그림자"
|
||||||
|
header: "헤더"
|
||||||
|
navBg: "사이드바 배경"
|
||||||
|
navFg: "사이드바 텍스트"
|
||||||
|
navHoverFg: "사이드바 텍스트 (호버)"
|
||||||
|
navActive: "사이드바 텍스트 (활성)"
|
||||||
|
navIndicator: "사이드바 인디케이터"
|
||||||
|
link: "링크"
|
||||||
|
hashtag: "해시태그"
|
||||||
|
mention: "멘션"
|
||||||
|
mentionMe: "나에게 보낸 멘션"
|
||||||
|
renote: "Renote"
|
||||||
|
modalBg: "모달 배경"
|
||||||
|
divider: "구분선"
|
||||||
|
scrollbarHandle: "스크롤바 핸들"
|
||||||
|
scrollbarHandleHover: "스크롤바 핸들 (호버)"
|
||||||
|
dateLabelFg: "날짜 레이블 텍스트"
|
||||||
|
infoBg: "정보창 배경"
|
||||||
|
infoFg: "정보창 텍스트"
|
||||||
|
infoWarnBg: "경고창 배경"
|
||||||
|
infoWarnFg: "경고창 텍스트"
|
||||||
|
cwBg: "CW 버튼 배경"
|
||||||
|
cwFg: "CW 버튼 텍스트"
|
||||||
|
cwHoverBg: "CW 버튼 배경 (호버)"
|
||||||
|
toastBg: "알림창 배경"
|
||||||
|
toastFg: "알림창 텍스트"
|
||||||
|
buttonBg: "버튼 배경"
|
||||||
|
buttonHoverBg: "버튼 배경 (호버)"
|
||||||
|
inputBorder: "입력 필드 테두리"
|
||||||
|
listItemHoverBg: "리스트 항목 배경 (호버)"
|
||||||
|
driveFolderBg: "드라이브 폴더 배경"
|
||||||
|
wallpaperOverlay: "배경화면 오버레이"
|
||||||
|
badge: "배지"
|
||||||
|
messageBg: "채팅 배경"
|
||||||
|
accentDarken: "강조 색상 (어두움)"
|
||||||
|
accentLighten: "강조 색상 (밝음)"
|
||||||
|
fgHighlighted: "강조된 텍스트"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "새 노트"
|
note: "새 노트"
|
||||||
noteMy: "내 노트"
|
noteMy: "내 노트"
|
||||||
|
@ -911,6 +1086,29 @@ _time:
|
||||||
minute: "분"
|
minute: "분"
|
||||||
hour: "시간"
|
hour: "시간"
|
||||||
day: "일"
|
day: "일"
|
||||||
|
_tutorial:
|
||||||
|
title: "FoundKey의 사용 방법"
|
||||||
|
step1_1: "환영합니다!"
|
||||||
|
step1_2: "이 페이지는 \"타임라인\"이라고 불립니다. 당신이 \"팔로우\"하고 있는 사람들의 \"노트\"가 시간순으로 나타납니다."
|
||||||
|
step1_3: "아직 아무 유저도 팔로우하고 있지 않기에 타임라인은 비어 있을 것입니다."
|
||||||
|
step2_1: "새 노트를 작성하거나 다른 사람을 팔로우하기 전에, 먼저 프로필을 완성해보도록 합시다."
|
||||||
|
step2_2: "당신이 어떤 사람인지를 알린다면, 다른 사람들이 당신을 팔로우할 확률이 올라갈 것입니다."
|
||||||
|
step3_1: "프로필 설정은 잘 끝내셨나요?"
|
||||||
|
step3_2: "그럼 시험삼아 노트를 작성해 보세요. 화면에 있는 연필 버튼을 누르면 작성 폼이 열립니다."
|
||||||
|
step3_3: "내용을 작성한 후, 폼 오른쪽 상단의 버튼을 눌러 노트를 올릴 수 있습니다."
|
||||||
|
step3_4: "쓸 말이 없나요? \"Misskey 시작했어요!\" 같은 건 어떨까요? :>"
|
||||||
|
step4_1: "노트 작성을 끝내셨나요?"
|
||||||
|
step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
|
||||||
|
step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다."
|
||||||
|
step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요. 마음에 드는 사람을 골라 팔로우해 보세요!"
|
||||||
|
step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요."
|
||||||
|
step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다."
|
||||||
|
step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다."
|
||||||
|
step6_2: "다른 유저의 노트에 \"리액션\"을 붙여 간단하게 당신의 반응을 전달할 수도 있습니다."
|
||||||
|
step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
|
||||||
|
step7_1: "이것으로 FoundKey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!"
|
||||||
|
step7_2: "FoundKey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요."
|
||||||
|
step7_3: "그럼 FoundKey를 즐기세요! 🚀"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "이미 설정이 완료되었습니다."
|
alreadyRegistered: "이미 설정이 완료되었습니다."
|
||||||
registerDevice: "디바이스 등록"
|
registerDevice: "디바이스 등록"
|
||||||
|
@ -920,8 +1118,7 @@ _2fa:
|
||||||
step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
|
step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
|
||||||
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
|
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
|
||||||
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
|
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
|
||||||
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할\
|
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
|
||||||
\ 수 있습니다."
|
|
||||||
_permissions:
|
_permissions:
|
||||||
"read:account": "계정의 정보를 봅니다"
|
"read:account": "계정의 정보를 봅니다"
|
||||||
"write:account": "계정의 정보를 변경합니다"
|
"write:account": "계정의 정보를 변경합니다"
|
||||||
|
@ -951,6 +1148,10 @@ _permissions:
|
||||||
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
||||||
"read:channels": "채널을 보기"
|
"read:channels": "채널을 보기"
|
||||||
"write:channels": "채널을 추가하거나 삭제합니다"
|
"write:channels": "채널을 추가하거나 삭제합니다"
|
||||||
|
"read:gallery": "갤러리를 봅니다"
|
||||||
|
"write:gallery": "갤러리를 추가하거나 삭제합니다"
|
||||||
|
"read:gallery-likes": "갤러리의 좋아요를 확인합니다"
|
||||||
|
"write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
|
@ -1127,6 +1328,7 @@ _relayStatus:
|
||||||
accepted: "승인됨"
|
accepted: "승인됨"
|
||||||
rejected: "거절됨"
|
rejected: "거절됨"
|
||||||
_notification:
|
_notification:
|
||||||
|
fileUploaded: "파일이 업로드되었습니다"
|
||||||
youGotMention: "{name}님이 멘션함"
|
youGotMention: "{name}님이 멘션함"
|
||||||
youGotReply: "{name}님이 답글함"
|
youGotReply: "{name}님이 답글함"
|
||||||
youGotQuote: "{name}님이 인용함"
|
youGotQuote: "{name}님이 인용함"
|
||||||
|
@ -1141,6 +1343,7 @@ _notification:
|
||||||
pollEnded: "투표 결과가 발표되었습니다"
|
pollEnded: "투표 결과가 발표되었습니다"
|
||||||
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
|
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
|
||||||
_types:
|
_types:
|
||||||
|
all: "전부"
|
||||||
follow: "팔로잉"
|
follow: "팔로잉"
|
||||||
mention: "멘션"
|
mention: "멘션"
|
||||||
reply: "답글"
|
reply: "답글"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registreren"
|
||||||
save: "Opslaan"
|
save: "Opslaan"
|
||||||
users: "Gebruikers"
|
users: "Gebruikers"
|
||||||
addUser: "Toevoegen gebruiker"
|
addUser: "Toevoegen gebruiker"
|
||||||
|
favorite: "Favorieten"
|
||||||
|
favorites: "Toevoegen aan favorieten"
|
||||||
|
unfavorite: "Verwijderen uit favorieten"
|
||||||
pin: "Vastmaken aan profielpagina"
|
pin: "Vastmaken aan profielpagina"
|
||||||
unpin: "Losmaken van profielpagina"
|
unpin: "Losmaken van profielpagina"
|
||||||
copyContent: "Kopiëren inhoud"
|
copyContent: "Kopiëren inhoud"
|
||||||
|
@ -224,6 +227,7 @@ uploadFromUrl: "Uploaden vanaf een URL"
|
||||||
uploadFromUrlDescription: "URL van het bestand dat je wil uploaden"
|
uploadFromUrlDescription: "URL van het bestand dat je wil uploaden"
|
||||||
uploadFromUrlRequested: "Uploadverzoek"
|
uploadFromUrlRequested: "Uploadverzoek"
|
||||||
uploadFromUrlMayTakeTime: "Het kan even duren voordat het uploaden voltooid is."
|
uploadFromUrlMayTakeTime: "Het kan even duren voordat het uploaden voltooid is."
|
||||||
|
explore: "Verkennen"
|
||||||
messageRead: "Lezen"
|
messageRead: "Lezen"
|
||||||
noMoreHistory: "Er is geen verdere geschiedenis"
|
noMoreHistory: "Er is geen verdere geschiedenis"
|
||||||
startMessaging: "Start een gesprek"
|
startMessaging: "Start een gesprek"
|
||||||
|
|
|
@ -33,6 +33,9 @@ signup: "Zarejestruj się"
|
||||||
save: "Zapisz"
|
save: "Zapisz"
|
||||||
users: "Użytkownicy"
|
users: "Użytkownicy"
|
||||||
addUser: "Dodaj użytkownika"
|
addUser: "Dodaj użytkownika"
|
||||||
|
favorite: "Dodaj do ulubionych"
|
||||||
|
favorites: "Ulubione"
|
||||||
|
unfavorite: "Usuń z ulubionych"
|
||||||
pin: "Przypnij do profilu"
|
pin: "Przypnij do profilu"
|
||||||
unpin: "Odepnij z profilu"
|
unpin: "Odepnij z profilu"
|
||||||
copyContent: "Skopiuj zawartość"
|
copyContent: "Skopiuj zawartość"
|
||||||
|
@ -225,6 +228,7 @@ uploadFromUrl: "Wyślij z adresu URL"
|
||||||
uploadFromUrlDescription: "Adres URL pliku, który chcesz wysłać"
|
uploadFromUrlDescription: "Adres URL pliku, który chcesz wysłać"
|
||||||
uploadFromUrlRequested: "Zażądano wysłania"
|
uploadFromUrlRequested: "Zażądano wysłania"
|
||||||
uploadFromUrlMayTakeTime: "Wysyłanie może chwilę potrwać."
|
uploadFromUrlMayTakeTime: "Wysyłanie może chwilę potrwać."
|
||||||
|
explore: "Eksploruj"
|
||||||
messageRead: "Przeczytano"
|
messageRead: "Przeczytano"
|
||||||
noMoreHistory: "Nie ma dalszej historii"
|
noMoreHistory: "Nie ma dalszej historii"
|
||||||
startMessaging: "Rozpocznij czat"
|
startMessaging: "Rozpocznij czat"
|
||||||
|
@ -306,6 +310,9 @@ inMb: "W megabajtach"
|
||||||
iconUrl: "Adres URL ikony"
|
iconUrl: "Adres URL ikony"
|
||||||
bannerUrl: "Adres URL banera"
|
bannerUrl: "Adres URL banera"
|
||||||
backgroundImageUrl: "Adres URL tła"
|
backgroundImageUrl: "Adres URL tła"
|
||||||
|
pinnedUsers: "Przypięty użytkownik"
|
||||||
|
pinnedUsersDescription: "Wypisz po jednej nazwie użytkownika w wierszu. Podani użytkownicy\
|
||||||
|
\ zostaną przypięci pod kartą „Eksploruj”."
|
||||||
hcaptchaSiteKey: "Klucz strony"
|
hcaptchaSiteKey: "Klucz strony"
|
||||||
hcaptchaSecretKey: "Tajny klucz"
|
hcaptchaSecretKey: "Tajny klucz"
|
||||||
recaptchaSiteKey: "Klucz strony"
|
recaptchaSiteKey: "Klucz strony"
|
||||||
|
@ -330,6 +337,11 @@ silence: "Wycisz"
|
||||||
silenceConfirm: "Czy na pewno chcesz wyciszyć tego użytkownika?"
|
silenceConfirm: "Czy na pewno chcesz wyciszyć tego użytkownika?"
|
||||||
unsilence: "Cofnij wyciszenie"
|
unsilence: "Cofnij wyciszenie"
|
||||||
unsilenceConfirm: "Czy na pewno chcesz cofnąć wyciszenie tego użytkownika?"
|
unsilenceConfirm: "Czy na pewno chcesz cofnąć wyciszenie tego użytkownika?"
|
||||||
|
popularUsers: "Popularni użytkownicy"
|
||||||
|
recentlyUpdatedUsers: "Ostatnio aktywni użytkownicy"
|
||||||
|
recentlyRegisteredUsers: "Ostatnio zarejestrowani użytkownicy"
|
||||||
|
recentlyDiscoveredUsers: "Ostatnio odkryci użytkownicy"
|
||||||
|
popularTags: "Tagi na czasie"
|
||||||
userList: "Listy"
|
userList: "Listy"
|
||||||
aboutMisskey: "O Foundkey"
|
aboutMisskey: "O Foundkey"
|
||||||
administrator: "Admin"
|
administrator: "Admin"
|
||||||
|
@ -370,6 +382,7 @@ messagingWithGroup: "Rozmowy wewnątrz grupy"
|
||||||
title: "Tytuł"
|
title: "Tytuł"
|
||||||
text: "Tekst"
|
text: "Tekst"
|
||||||
enable: "Włącz"
|
enable: "Włącz"
|
||||||
|
next: "Dalej"
|
||||||
retype: "Wprowadź ponownie"
|
retype: "Wprowadź ponownie"
|
||||||
noteOf: "Wpisy {user}"
|
noteOf: "Wpisy {user}"
|
||||||
inviteToGroup: "Zaproś do grupy"
|
inviteToGroup: "Zaproś do grupy"
|
||||||
|
@ -546,6 +559,7 @@ abuseReports: "Zgłoszenia"
|
||||||
reportAbuse: "Zgłoś"
|
reportAbuse: "Zgłoś"
|
||||||
reportAbuseOf: "Zgłoś {name}"
|
reportAbuseOf: "Zgłoś {name}"
|
||||||
fillAbuseReportDescription: "Wypełnij szczegóły zgłoszenia."
|
fillAbuseReportDescription: "Wypełnij szczegóły zgłoszenia."
|
||||||
|
abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy."
|
||||||
reporteeOrigin: "Pochodzenie osoby zgłoszonej"
|
reporteeOrigin: "Pochodzenie osoby zgłoszonej"
|
||||||
reporterOrigin: "Pochodzenie osoby zgłaszającej"
|
reporterOrigin: "Pochodzenie osoby zgłaszającej"
|
||||||
send: "Wyślij"
|
send: "Wyślij"
|
||||||
|
@ -590,6 +604,7 @@ disableShowingAnimatedImages: "Nie odtwarzaj animowanych obrazów"
|
||||||
verificationEmailSent: "Wiadomość weryfikacyjna została wysłana. Odwiedź uwzględniony\
|
verificationEmailSent: "Wiadomość weryfikacyjna została wysłana. Odwiedź uwzględniony\
|
||||||
\ odnośnik, aby ukończyć weryfikację."
|
\ odnośnik, aby ukończyć weryfikację."
|
||||||
emailVerified: "Adres e-mail został potwierdzony"
|
emailVerified: "Adres e-mail został potwierdzony"
|
||||||
|
noteFavoritesCount: "Liczba polubionych wpisów"
|
||||||
pageLikesCount: "Liczba otrzymanych polubień stron"
|
pageLikesCount: "Liczba otrzymanych polubień stron"
|
||||||
pageLikedCount: "Liczba polubionych stron"
|
pageLikedCount: "Liczba polubionych stron"
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
@ -846,6 +861,40 @@ _time:
|
||||||
minute: "minuta"
|
minute: "minuta"
|
||||||
hour: "godz."
|
hour: "godz."
|
||||||
day: "dzień"
|
day: "dzień"
|
||||||
|
_tutorial:
|
||||||
|
title: "Jak korzystać z Foundkey"
|
||||||
|
step1_1: "Witaj!"
|
||||||
|
step1_3: "Twoja oś czasu jest jeszcze pusta, ponieważ nie opublikował*ś jeszcze\
|
||||||
|
\ żadnych wpisów i nie obserwujesz jeszcze nikogo."
|
||||||
|
step2_1: "Ukończmy konfigurację profilu zanim utworzymy wpis lub zaczniemy kogoś\
|
||||||
|
\ obserwować."
|
||||||
|
step3_1: "Zakończył*ś konfigurację profilu?"
|
||||||
|
step3_3: "Wypełnij pole i kliknij przycisk w prawym górnym rogu by wysłać wpis."
|
||||||
|
step4_1: Skończył*ś publikować swój pierwszy wpis?
|
||||||
|
step6_2: Możesz też dodawać reakcję do wpisów innych ludzi, żeby szybko odpowiedzieć
|
||||||
|
na nie.
|
||||||
|
step4_2: Hura! Teraz Twój wpis powinien być widoczny na Twojej osi czasu.
|
||||||
|
step5_2: '{featured} pokaże CI popularne wpisy z tej instancji. {explore} pozwoli
|
||||||
|
ci znaleźć popularnych użytkowników. Spróbuj tam znaleźć ludzi których chciał*byś
|
||||||
|
zaobserwować!'
|
||||||
|
step7_1: Gratulacje! Właśnie ukończył*ś podstawowy samouczek Foundkey.
|
||||||
|
step6_3: W celu dodania reakcji, kliknij na "+" przy poście innego użytkownika i
|
||||||
|
wybierz jakieś emoji jako reakcję na niego.
|
||||||
|
step6_1: Wpisy innych ludzi powinny już się pojawiać na Twojej osi czasu.
|
||||||
|
step7_2: Jeśli chcesz dowiedzieć się więcej o Foundkey, przejdź do sekcji {help}.
|
||||||
|
step7_3: A teraz, powodzenia i miłej zabawy z Foundkey! 🚀
|
||||||
|
step5_1: Teraz spróbujmy ożywić oś czasu poprzez zaobserwowanie innych ludzi.
|
||||||
|
step1_2: Ta strona jest nazywana "osią czasu". Pokazuje ona chronologicznie ułożone
|
||||||
|
wpisy ludzi których obserwujesz.
|
||||||
|
step5_4: Jeśli inny użytkownik ma kłódkę przy nazwie, ręczne zatwierdzenie Twojej
|
||||||
|
prośby o obserwację przez tego użytkownika może zająć trochę czasu.
|
||||||
|
step3_2: Spróbujmy opublikować wpis. Możesz to zrobić klikając przycisk z ikoną
|
||||||
|
ołówka.
|
||||||
|
step2_2: Dodanie informacji o sobie pomoże innym w decyzji czy chcą widzieć Twoje
|
||||||
|
wpisy, lub Ciebie obserwować.
|
||||||
|
step5_3: By zaobserwować innych użytkowników, kliknij na ich ikonę i wciśnij przycisk
|
||||||
|
"Obserwuj" na ich profilu.
|
||||||
|
step3_4: Nic nie przychodzi na myśl? Spróbuj "Właśnie wrócił*m z kościoła"!
|
||||||
_2fa:
|
_2fa:
|
||||||
registerDevice: "Zarejestruj nowe urządzenie"
|
registerDevice: "Zarejestruj nowe urządzenie"
|
||||||
step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b})\
|
step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b})\
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registrar-se"
|
||||||
save: "Guardar"
|
save: "Guardar"
|
||||||
users: "Usuários"
|
users: "Usuários"
|
||||||
addUser: "Adicionar usuário"
|
addUser: "Adicionar usuário"
|
||||||
|
favorite: "Favoritar"
|
||||||
|
favorites: "Favoritar"
|
||||||
|
unfavorite: "Remover dos favoritos"
|
||||||
pin: "Afixar no perfil"
|
pin: "Afixar no perfil"
|
||||||
unpin: "Desafixar do perfil"
|
unpin: "Desafixar do perfil"
|
||||||
copyContent: "Copiar conteúdos"
|
copyContent: "Copiar conteúdos"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Înregistrează-te"
|
||||||
save: "Salvează"
|
save: "Salvează"
|
||||||
users: "Utilizatori"
|
users: "Utilizatori"
|
||||||
addUser: "Adăugă utilizator"
|
addUser: "Adăugă utilizator"
|
||||||
|
favorite: "Adaugă la favorite"
|
||||||
|
favorites: "Favorite"
|
||||||
|
unfavorite: "Elimină din favorite"
|
||||||
pin: "Fixează pe profil"
|
pin: "Fixează pe profil"
|
||||||
unpin: "Anulati fixare"
|
unpin: "Anulati fixare"
|
||||||
copyContent: "Copiază conținutul"
|
copyContent: "Copiază conținutul"
|
||||||
|
@ -236,6 +239,7 @@ uploadFromUrl: "Încarcă dintr-un URL"
|
||||||
uploadFromUrlDescription: "URL-ul fișierului pe care dorești să îl încarci"
|
uploadFromUrlDescription: "URL-ul fișierului pe care dorești să îl încarci"
|
||||||
uploadFromUrlRequested: "Încărcare solicitată"
|
uploadFromUrlRequested: "Încărcare solicitată"
|
||||||
uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează încărcarea."
|
uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează încărcarea."
|
||||||
|
explore: "Explorează"
|
||||||
messageRead: "Citit"
|
messageRead: "Citit"
|
||||||
noMoreHistory: "Nu există mai mult istoric"
|
noMoreHistory: "Nu există mai mult istoric"
|
||||||
startMessaging: "Începe un chat nou"
|
startMessaging: "Începe un chat nou"
|
||||||
|
@ -317,6 +321,9 @@ inMb: "În megabytes"
|
||||||
iconUrl: "URL-ul iconiței"
|
iconUrl: "URL-ul iconiței"
|
||||||
bannerUrl: "URL-ul imaginii de banner"
|
bannerUrl: "URL-ul imaginii de banner"
|
||||||
backgroundImageUrl: "URL-ul imaginii de fundal"
|
backgroundImageUrl: "URL-ul imaginii de fundal"
|
||||||
|
pinnedUsers: "Utilizatori fixați"
|
||||||
|
pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor\
|
||||||
|
\ fi fixați pe pagina \"Explorează\"."
|
||||||
hcaptchaSiteKey: "Site key"
|
hcaptchaSiteKey: "Site key"
|
||||||
hcaptchaSecretKey: "Secret key"
|
hcaptchaSecretKey: "Secret key"
|
||||||
recaptchaSiteKey: "Site key"
|
recaptchaSiteKey: "Site key"
|
||||||
|
@ -341,6 +348,11 @@ silence: "Amuțește"
|
||||||
silenceConfirm: "Ești sigur că vrei să amuțești acest utilizator?"
|
silenceConfirm: "Ești sigur că vrei să amuțești acest utilizator?"
|
||||||
unsilence: "Anulează amuțirea"
|
unsilence: "Anulează amuțirea"
|
||||||
unsilenceConfirm: "Ești sigur că vrei să anulezi amuțirea acestui utilizator?"
|
unsilenceConfirm: "Ești sigur că vrei să anulezi amuțirea acestui utilizator?"
|
||||||
|
popularUsers: "Utilizatori populari"
|
||||||
|
recentlyUpdatedUsers: "Utilizatori activi recent"
|
||||||
|
recentlyRegisteredUsers: "Utilizatori ce s-au alăturat recent"
|
||||||
|
recentlyDiscoveredUsers: "Utilizatori descoperiți recent"
|
||||||
|
popularTags: "Taguri populare"
|
||||||
userList: "Liste"
|
userList: "Liste"
|
||||||
aboutMisskey: "Despre FoundKey"
|
aboutMisskey: "Despre FoundKey"
|
||||||
administrator: "Administrator"
|
administrator: "Administrator"
|
||||||
|
@ -381,6 +393,7 @@ messagingWithGroup: "Chat de grup"
|
||||||
title: "Titlu"
|
title: "Titlu"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Activează"
|
enable: "Activează"
|
||||||
|
next: "Următorul"
|
||||||
retype: "Introdu din nou"
|
retype: "Introdu din nou"
|
||||||
noteOf: "Notă de {user}"
|
noteOf: "Notă de {user}"
|
||||||
inviteToGroup: "Invită în grup"
|
inviteToGroup: "Invită în grup"
|
||||||
|
@ -574,6 +587,7 @@ abuseReports: "Rapoarte"
|
||||||
reportAbuse: "Raportează"
|
reportAbuse: "Raportează"
|
||||||
reportAbuseOf: "Raportează {name}"
|
reportAbuseOf: "Raportează {name}"
|
||||||
fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport."
|
fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport."
|
||||||
|
abuseReported: "Raportul tău a fost trimis. Mulțumim."
|
||||||
reporter: "Raportorul"
|
reporter: "Raportorul"
|
||||||
reporteeOrigin: "Originea raportatului"
|
reporteeOrigin: "Originea raportatului"
|
||||||
reporterOrigin: "Originea raportorului"
|
reporterOrigin: "Originea raportorului"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Регистрация"
|
||||||
save: "Сохранить"
|
save: "Сохранить"
|
||||||
users: "Пользователи"
|
users: "Пользователи"
|
||||||
addUser: "Добавить пользователя"
|
addUser: "Добавить пользователя"
|
||||||
|
favorite: "В избранное"
|
||||||
|
favorites: "Избранное"
|
||||||
|
unfavorite: "Убрать из избранного"
|
||||||
pin: "Закрепить в профиле"
|
pin: "Закрепить в профиле"
|
||||||
unpin: "Открепить от профиля"
|
unpin: "Открепить от профиля"
|
||||||
copyContent: "Скопировать содержимое"
|
copyContent: "Скопировать содержимое"
|
||||||
|
@ -231,6 +234,7 @@ uploadFromUrl: "Загрузить по ссылке"
|
||||||
uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
|
uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
|
||||||
uploadFromUrlRequested: "Загрузка выбранного"
|
uploadFromUrlRequested: "Загрузка выбранного"
|
||||||
uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время."
|
uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время."
|
||||||
|
explore: "Обзор"
|
||||||
messageRead: "Прочитали"
|
messageRead: "Прочитали"
|
||||||
noMoreHistory: "История закончилась"
|
noMoreHistory: "История закончилась"
|
||||||
startMessaging: "Начать общение"
|
startMessaging: "Начать общение"
|
||||||
|
@ -311,6 +315,9 @@ inMb: "В мегабайтах"
|
||||||
iconUrl: "Ссылка на аватар"
|
iconUrl: "Ссылка на аватар"
|
||||||
bannerUrl: "Ссылка на изображение в шапке"
|
bannerUrl: "Ссылка на изображение в шапке"
|
||||||
backgroundImageUrl: "Ссылка на фоновое изображение"
|
backgroundImageUrl: "Ссылка на фоновое изображение"
|
||||||
|
pinnedUsers: "Прикреплённый пользователь"
|
||||||
|
pinnedUsersDescription: "Перечислите по одному имени пользователя в строке. Пользователи,\
|
||||||
|
\ перечисленные здесь, будут привязаны к закладке \"Изучение\"."
|
||||||
hcaptchaSiteKey: "Ключ сайта"
|
hcaptchaSiteKey: "Ключ сайта"
|
||||||
hcaptchaSecretKey: "Секретный ключ"
|
hcaptchaSecretKey: "Секретный ключ"
|
||||||
recaptchaSiteKey: "Ключ сайта"
|
recaptchaSiteKey: "Ключ сайта"
|
||||||
|
@ -336,6 +343,11 @@ silence: "Заглушить"
|
||||||
silenceConfirm: "Заглушить этого пользователя? Уверены?"
|
silenceConfirm: "Заглушить этого пользователя? Уверены?"
|
||||||
unsilence: "Снять глушение"
|
unsilence: "Снять глушение"
|
||||||
unsilenceConfirm: "Снять глушение с этого пользователя? Уверены?"
|
unsilenceConfirm: "Снять глушение с этого пользователя? Уверены?"
|
||||||
|
popularUsers: "Популярные пользователи"
|
||||||
|
recentlyUpdatedUsers: "Активные последнее время"
|
||||||
|
recentlyRegisteredUsers: "Недавно зарегистрированные пользователи"
|
||||||
|
recentlyDiscoveredUsers: "Недавно обнаруженные пользователи"
|
||||||
|
popularTags: "Популярные теги"
|
||||||
userList: "Списки"
|
userList: "Списки"
|
||||||
aboutMisskey: "О FoundKey"
|
aboutMisskey: "О FoundKey"
|
||||||
administrator: "Администратор"
|
administrator: "Администратор"
|
||||||
|
@ -376,6 +388,7 @@ messagingWithGroup: "Общение в группе"
|
||||||
title: "Заголовок"
|
title: "Заголовок"
|
||||||
text: "Текст"
|
text: "Текст"
|
||||||
enable: "Включить"
|
enable: "Включить"
|
||||||
|
next: "Дальше"
|
||||||
retype: "Введите ещё раз"
|
retype: "Введите ещё раз"
|
||||||
noteOf: "Что пишет {user}"
|
noteOf: "Что пишет {user}"
|
||||||
inviteToGroup: "Пригласить в группу"
|
inviteToGroup: "Пригласить в группу"
|
||||||
|
@ -569,6 +582,7 @@ abuseReports: "Жалобы"
|
||||||
reportAbuse: "Жалоба"
|
reportAbuse: "Жалоба"
|
||||||
reportAbuseOf: "Пожаловаться на пользователя {name}"
|
reportAbuseOf: "Пожаловаться на пользователя {name}"
|
||||||
fillAbuseReportDescription: "Опишите, пожалуйста, причину жалобы подробнее."
|
fillAbuseReportDescription: "Опишите, пожалуйста, причину жалобы подробнее."
|
||||||
|
abuseReported: "Жалоба отправлена. Большое спасибо за информацию."
|
||||||
reporteeOrigin: "О ком сообщено"
|
reporteeOrigin: "О ком сообщено"
|
||||||
reporterOrigin: "Кто сообщил"
|
reporterOrigin: "Кто сообщил"
|
||||||
forwardReport: "Перенаправление отчета на инстант."
|
forwardReport: "Перенаправление отчета на инстант."
|
||||||
|
@ -617,6 +631,7 @@ disableShowingAnimatedImages: "Не проигрывать анимацию"
|
||||||
verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста,\
|
verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста,\
|
||||||
\ по ссылке из письма, чтобы завершить проверку."
|
\ по ссылке из письма, чтобы завершить проверку."
|
||||||
emailVerified: "Адрес электронной почты подтверждён."
|
emailVerified: "Адрес электронной почты подтверждён."
|
||||||
|
noteFavoritesCount: "Количество добавленного в избранное"
|
||||||
pageLikesCount: "Количество понравившихся страниц"
|
pageLikesCount: "Количество понравившихся страниц"
|
||||||
pageLikedCount: "Количество страниц, понравившихся другим"
|
pageLikedCount: "Количество страниц, понравившихся другим"
|
||||||
contact: "Как связаться"
|
contact: "Как связаться"
|
||||||
|
@ -952,6 +967,43 @@ _time:
|
||||||
minute: "мин"
|
minute: "мин"
|
||||||
hour: "ч"
|
hour: "ч"
|
||||||
day: "сут"
|
day: "сут"
|
||||||
|
_tutorial:
|
||||||
|
title: "Как пользоваться FoundKey"
|
||||||
|
step1_1: "Добро пожаловать!"
|
||||||
|
step1_2: "Эта страница называется «лента». Здесь будут появляться «заметки»: ваши\
|
||||||
|
\ личные и тех, на кого вы «подписаны». Они будут располагаться в порядке времени\
|
||||||
|
\ их появления."
|
||||||
|
step1_3: "Правда, ваша лента пока пуста. Она начнёт заполняться, когда вы будете\
|
||||||
|
\ писать свои заметки и подписываться на других."
|
||||||
|
step2_1: "Давайте, заполним профиль, прежде чем начать писать заметки и подписываться\
|
||||||
|
\ на других."
|
||||||
|
step2_2: "То, что вы расскажете в профиле, поможет лучше вас узнать, а значит, многим\
|
||||||
|
\ будет легче присоединиться — вы скорее получите новых подписчиков и читателей."
|
||||||
|
step3_1: "Успешно заполнили профиль?"
|
||||||
|
step3_2: "Что ж, теперь самое время опубликуовать заметку. Если нажать вверху страницы\
|
||||||
|
\ на изображение карандаша, появится форма для текста."
|
||||||
|
step3_3: "Напишите в неё, что хотите, и нажмите на кнопку в правом верхнем углу."
|
||||||
|
step3_4: "Ничего не приходит в голову? Как насчёт: «Я новенький, пока осваиваюсь\
|
||||||
|
\ в FoundKey»?"
|
||||||
|
step4_1: "С написанием первой заметки покончено?"
|
||||||
|
step4_2: "Отлично, теперь она должна появиться в вашей ленте."
|
||||||
|
step5_1: "А теперь самое время немного оживить ленту, подписавшись на других."
|
||||||
|
step5_2: "На странице «{featured}» собраны популярные сегодня заметки, читая которые,\
|
||||||
|
\ вы можете найти кого-то вам интересного, а на странице «{explore}» можно посмотреть,\
|
||||||
|
\ кто популярен у остальных."
|
||||||
|
step5_3: "Чтобы подписаться на кого-нибудь, щёлкните по его аватару и в открывшемся\
|
||||||
|
\ профиле нажмите кнопку «Подписаться»."
|
||||||
|
step5_4: "Некоторые пользователи (около их имени «висит замок») вручную подтверждают\
|
||||||
|
\ чужие подписки. Так что иногда подписка начинает работать не сразу."
|
||||||
|
step6_1: "Если теперь в ленте видны и чужие заметки, значит у вас получилось."
|
||||||
|
step6_2: "Здесь можно непринуждённо выразить свои чувства к чьей-то заметке, отметив\
|
||||||
|
\ «реакцию» под ней."
|
||||||
|
step6_3: "Отмечайте реакции, нажмая на символ «+» под заметкой и выбирая значок\
|
||||||
|
\ по душе."
|
||||||
|
step7_1: "На этом вводный урок по использованию FoundKey закончен. Спасибо, что\
|
||||||
|
\ прошли его до конца!"
|
||||||
|
step7_2: "Хотите изучить FoundKey глубже — добро пожаловать в раздел «{help}»."
|
||||||
|
step7_3: "Приятно вам провести время с FoundKey\U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Двухфакторная аутентификация уже настроена."
|
alreadyRegistered: "Двухфакторная аутентификация уже настроена."
|
||||||
registerDevice: "Зарегистрируйте ваше устройство"
|
registerDevice: "Зарегистрируйте ваше устройство"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registrovať"
|
||||||
save: "Uložiť"
|
save: "Uložiť"
|
||||||
users: "Používatelia"
|
users: "Používatelia"
|
||||||
addUser: "Pridať používateľa"
|
addUser: "Pridať používateľa"
|
||||||
|
favorite: "Páči sa mi"
|
||||||
|
favorites: "Obľúbené"
|
||||||
|
unfavorite: "Nepáči sa mi"
|
||||||
pin: "Pripnúť"
|
pin: "Pripnúť"
|
||||||
unpin: "Odopnúť"
|
unpin: "Odopnúť"
|
||||||
copyContent: "Kopírovať obsah"
|
copyContent: "Kopírovať obsah"
|
||||||
|
@ -230,6 +233,7 @@ uploadFromUrl: "Nahrať z URL adresy"
|
||||||
uploadFromUrlDescription: "URL adresa nahrávaného súboru"
|
uploadFromUrlDescription: "URL adresa nahrávaného súboru"
|
||||||
uploadFromUrlRequested: "Upload vyžiadaný"
|
uploadFromUrlRequested: "Upload vyžiadaný"
|
||||||
uploadFromUrlMayTakeTime: "Nahrávanie môže nejaký čas trvať."
|
uploadFromUrlMayTakeTime: "Nahrávanie môže nejaký čas trvať."
|
||||||
|
explore: "Objavovať"
|
||||||
messageRead: "Prečítané"
|
messageRead: "Prečítané"
|
||||||
noMoreHistory: "To je všetko"
|
noMoreHistory: "To je všetko"
|
||||||
startMessaging: "Začať chat"
|
startMessaging: "Začať chat"
|
||||||
|
@ -311,6 +315,9 @@ inMb: "V megabajtoch"
|
||||||
iconUrl: "Favicon URL"
|
iconUrl: "Favicon URL"
|
||||||
bannerUrl: "URL obrázku bannera"
|
bannerUrl: "URL obrázku bannera"
|
||||||
backgroundImageUrl: "URL obrázku pozadia"
|
backgroundImageUrl: "URL obrázku pozadia"
|
||||||
|
pinnedUsers: "Pripnutí používatelia"
|
||||||
|
pinnedUsersDescription: "Zoznam mien používateľov oddelených riadkami, ktorý budú\
|
||||||
|
\ pripnutí v záložke \"Objavovať\"."
|
||||||
hcaptchaSiteKey: "Site key"
|
hcaptchaSiteKey: "Site key"
|
||||||
hcaptchaSecretKey: "Secret key"
|
hcaptchaSecretKey: "Secret key"
|
||||||
recaptchaSiteKey: "Site key"
|
recaptchaSiteKey: "Site key"
|
||||||
|
@ -335,6 +342,11 @@ silence: "Ticho"
|
||||||
silenceConfirm: "Naozaj chcete utíšiť tohoto používateľa?"
|
silenceConfirm: "Naozaj chcete utíšiť tohoto používateľa?"
|
||||||
unsilence: "Vrátiť utíšenie"
|
unsilence: "Vrátiť utíšenie"
|
||||||
unsilenceConfirm: "Naozaj chcete vrátiť utíšenie tohoto používateľa?"
|
unsilenceConfirm: "Naozaj chcete vrátiť utíšenie tohoto používateľa?"
|
||||||
|
popularUsers: "Populárni používatelia"
|
||||||
|
recentlyUpdatedUsers: "Používatelia s najnovšou aktivitou"
|
||||||
|
recentlyRegisteredUsers: "Najnovší používatelia"
|
||||||
|
recentlyDiscoveredUsers: "Naposledy objavení používatelia"
|
||||||
|
popularTags: "Populárne značky"
|
||||||
userList: "Zoznamy"
|
userList: "Zoznamy"
|
||||||
aboutMisskey: "O FoundKey"
|
aboutMisskey: "O FoundKey"
|
||||||
administrator: "Administrátor"
|
administrator: "Administrátor"
|
||||||
|
@ -375,6 +387,7 @@ messagingWithGroup: "Skupinový chat"
|
||||||
title: "Nadpis"
|
title: "Nadpis"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Povoliť"
|
enable: "Povoliť"
|
||||||
|
next: "Ďalší"
|
||||||
retype: "Zadajte znovu"
|
retype: "Zadajte znovu"
|
||||||
noteOf: "Poznámky používateľa {user}"
|
noteOf: "Poznámky používateľa {user}"
|
||||||
inviteToGroup: "Pozvať do skupiny"
|
inviteToGroup: "Pozvať do skupiny"
|
||||||
|
@ -560,6 +573,7 @@ abuseReports: "Nahlásenia"
|
||||||
reportAbuse: "Nahlásiť"
|
reportAbuse: "Nahlásiť"
|
||||||
reportAbuseOf: "Nahlásiť {name}"
|
reportAbuseOf: "Nahlásiť {name}"
|
||||||
fillAbuseReportDescription: "Prosím vyplňte podrobnosti nahlásenia."
|
fillAbuseReportDescription: "Prosím vyplňte podrobnosti nahlásenia."
|
||||||
|
abuseReported: "Vaše nahlásenie je odoslané. Veľmi pekne ďakujeme."
|
||||||
reporter: "Nahlásil"
|
reporter: "Nahlásil"
|
||||||
reporteeOrigin: "Pôvod nahláseného"
|
reporteeOrigin: "Pôvod nahláseného"
|
||||||
reporterOrigin: "Pôvod nahlasovača"
|
reporterOrigin: "Pôvod nahlasovača"
|
||||||
|
@ -609,6 +623,7 @@ disableShowingAnimatedImages: "Neprehrávať animované obrázky"
|
||||||
verificationEmailSent: "Odoslali sme overovací email. Overenie dokončíte kliknutím\
|
verificationEmailSent: "Odoslali sme overovací email. Overenie dokončíte kliknutím\
|
||||||
\ na odkaz v emaili."
|
\ na odkaz v emaili."
|
||||||
emailVerified: "Email overený"
|
emailVerified: "Email overený"
|
||||||
|
noteFavoritesCount: "Počet obľúbených poznámok"
|
||||||
pageLikesCount: "Počet obľúbených stránok"
|
pageLikesCount: "Počet obľúbených stránok"
|
||||||
pageLikedCount: "Počet prijatých \"páči sa mi\""
|
pageLikedCount: "Počet prijatých \"páči sa mi\""
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
@ -948,6 +963,40 @@ _time:
|
||||||
minute: "min"
|
minute: "min"
|
||||||
hour: "hod"
|
hour: "hod"
|
||||||
day: "dní"
|
day: "dní"
|
||||||
|
_tutorial:
|
||||||
|
title: "Ako používať FoundKey"
|
||||||
|
step1_1: "Vitajte!"
|
||||||
|
step1_2: "Táto stránka sa volá \"časová os\". Zobrazuje chronologicky zoradené \"\
|
||||||
|
poznámky\" od ľudí, ktorých sledujete."
|
||||||
|
step1_3: "Vaša časová os je teraz prázdna pretože ste nepridali žiadne poznámky\
|
||||||
|
\ ani nikoho zatiaľ nesledujete."
|
||||||
|
step2_1: "Podˇme dokončiť nastavenia vášho profilu pred napísaním poznámky alebo\
|
||||||
|
\ sledovaním niekoho."
|
||||||
|
step2_2: "Poskytnutím informácií o vás uľahčíte ostatným, či chcú vidieť alebo sledovať\
|
||||||
|
\ vaše poznámky."
|
||||||
|
step3_1: "Dokončili ste nastavovanie svojho profilu?"
|
||||||
|
step3_2: "Poďme vyskúšať napísať poznámku. Môžete to spraviť stlačením ikony ceruzky\
|
||||||
|
\ na vrchu obrazovky."
|
||||||
|
step3_3: "Vyplňte polia a stlačte tlačítko vpravo hore."
|
||||||
|
step3_4: "Nemáte čo povedať? Skúste \"len si nastavujem môj msky\"!"
|
||||||
|
step4_1: "Napísali ste svoju prvú poznámku?"
|
||||||
|
step4_2: "Hurá! Teraz by vaša prvá poznámka mala byť na vašej časovej osi."
|
||||||
|
step5_1: "Teraz skúsme oživiť časovú os sledovaním nejakých ľudí."
|
||||||
|
step5_2: "{featured} zobrazí populárne poznámku na tomto serveri. {explore} môžete\
|
||||||
|
\ objavovať populárnych používateľov. Skúste tam nájsť ľudí, ktorých by ste radi\
|
||||||
|
\ sledovali!"
|
||||||
|
step5_3: "Ak chcete sledovať ďalších používateľov, kliknite na ich ikonu a stlačte\
|
||||||
|
\ tlačidlo \"Sledovať\" na ich profile."
|
||||||
|
step5_4: "Ak má niektorý používateľ ikonu zámku vedľa svojho mena, znamená to, že\
|
||||||
|
\ môže trvať určitý čas, kým daný používateľ schváli vašu žiadosť o sledovanie."
|
||||||
|
step6_1: "Teraz by ste mali vidieť poznámky ďalších používateľov na svojej časovej\
|
||||||
|
\ osi."
|
||||||
|
step6_2: "Môžete dať \"reakcie\" na poznámky ďalších ľudí ako rýchlu odpoveď."
|
||||||
|
step6_3: "Reakciu pridáte kliknutím na \"+\" niekoho poznámke a vybratím emoji,\
|
||||||
|
\ ktorou chcete reagovať."
|
||||||
|
step7_1: "Gralujeme! Dokončili ste základného sprievodcu FoundKey."
|
||||||
|
step7_2: "Ak sa chcete naučiť viac o FoundKey, skúste sekciu {help}."
|
||||||
|
step7_3: "A teraz, veľa šťastia, bavte sa s FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikačné zariadenie."
|
alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikačné zariadenie."
|
||||||
registerDevice: "Registrovať nové zariadenie"
|
registerDevice: "Registrovať nové zariadenie"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Registrera"
|
||||||
save: "Spara"
|
save: "Spara"
|
||||||
users: "Användare"
|
users: "Användare"
|
||||||
addUser: "Lägg till användare"
|
addUser: "Lägg till användare"
|
||||||
|
favorite: "Lägg till i favoriter"
|
||||||
|
favorites: "Favoriter"
|
||||||
|
unfavorite: "Avfavorisera"
|
||||||
pin: "Fäst till profil"
|
pin: "Fäst till profil"
|
||||||
unpin: "Lossa från profil"
|
unpin: "Lossa från profil"
|
||||||
copyContent: "Kopiera innehåll"
|
copyContent: "Kopiera innehåll"
|
||||||
|
@ -308,6 +311,7 @@ uploadFromUrl: Ladda upp via en URL
|
||||||
uploadFromUrlDescription: URL till filen som du vill ladda upp
|
uploadFromUrlDescription: URL till filen som du vill ladda upp
|
||||||
uploadFromUrlRequested: Förfrågade uppladningar
|
uploadFromUrlRequested: Förfrågade uppladningar
|
||||||
uploadFromUrlMayTakeTime: Det tar kanske ett tag innan uppladningen är färdig.
|
uploadFromUrlMayTakeTime: Det tar kanske ett tag innan uppladningen är färdig.
|
||||||
|
explore: Upptäck
|
||||||
messageRead: Läs
|
messageRead: Läs
|
||||||
noMoreHistory: Det finns ingen mer historik
|
noMoreHistory: Det finns ingen mer historik
|
||||||
startMessaging: Inled en ny chatt
|
startMessaging: Inled en ny chatt
|
||||||
|
|
|
@ -28,6 +28,9 @@ logout: "Çıkış Yap"
|
||||||
signup: "Kayıt Ol"
|
signup: "Kayıt Ol"
|
||||||
users: "Kullanıcı"
|
users: "Kullanıcı"
|
||||||
addUser: "Kullanıcı Ekle"
|
addUser: "Kullanıcı Ekle"
|
||||||
|
favorite: "Favoriler"
|
||||||
|
favorites: "Favoriler"
|
||||||
|
unfavorite: "Favorilerden Kaldır"
|
||||||
pin: "Sabitlenmiş"
|
pin: "Sabitlenmiş"
|
||||||
unpin: "Sabitlemeyi kaldır"
|
unpin: "Sabitlemeyi kaldır"
|
||||||
copyContent: "İçeriği kopyala"
|
copyContent: "İçeriği kopyala"
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Реєстрація"
|
||||||
save: "Зберегти"
|
save: "Зберегти"
|
||||||
users: "Користувачі"
|
users: "Користувачі"
|
||||||
addUser: "Додати користувача"
|
addUser: "Додати користувача"
|
||||||
|
favorite: "Обране"
|
||||||
|
favorites: "Обране"
|
||||||
|
unfavorite: "Видалити з обраного"
|
||||||
pin: "Закріпити"
|
pin: "Закріпити"
|
||||||
unpin: "Відкріпити"
|
unpin: "Відкріпити"
|
||||||
copyContent: "Скопіювати контент"
|
copyContent: "Скопіювати контент"
|
||||||
|
@ -234,6 +237,7 @@ uploadFromUrl: "Завантажити з посилання"
|
||||||
uploadFromUrlDescription: "Посилання на файл для завантаження"
|
uploadFromUrlDescription: "Посилання на файл для завантаження"
|
||||||
uploadFromUrlRequested: "Завантаження розпочалось"
|
uploadFromUrlRequested: "Завантаження розпочалось"
|
||||||
uploadFromUrlMayTakeTime: "Завантаження може зайняти деякий час."
|
uploadFromUrlMayTakeTime: "Завантаження може зайняти деякий час."
|
||||||
|
explore: "Огляд"
|
||||||
messageRead: "Прочитано"
|
messageRead: "Прочитано"
|
||||||
noMoreHistory: "Подальшої історії немає"
|
noMoreHistory: "Подальшої історії немає"
|
||||||
startMessaging: "Розпочати діалог"
|
startMessaging: "Розпочати діалог"
|
||||||
|
@ -313,6 +317,9 @@ inMb: "В мегабайтах"
|
||||||
iconUrl: "URL аватара"
|
iconUrl: "URL аватара"
|
||||||
bannerUrl: "URL банера"
|
bannerUrl: "URL банера"
|
||||||
backgroundImageUrl: "URL-адреса фонового зображення"
|
backgroundImageUrl: "URL-адреса фонового зображення"
|
||||||
|
pinnedUsers: "Закріплені користувачі"
|
||||||
|
pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці\
|
||||||
|
\ \"Знайти\", ім'я в стовпчик."
|
||||||
hcaptchaSiteKey: "Ключ сайту"
|
hcaptchaSiteKey: "Ключ сайту"
|
||||||
hcaptchaSecretKey: "Секретний ключ"
|
hcaptchaSecretKey: "Секретний ключ"
|
||||||
recaptchaSiteKey: "Ключ сайту"
|
recaptchaSiteKey: "Ключ сайту"
|
||||||
|
@ -337,6 +344,11 @@ silence: "Заглушити"
|
||||||
silenceConfirm: "Ви впевнені, що хочете заглушити цього користувача?"
|
silenceConfirm: "Ви впевнені, що хочете заглушити цього користувача?"
|
||||||
unsilence: "Не глушити"
|
unsilence: "Не глушити"
|
||||||
unsilenceConfirm: "Ви впевнені, що хочете скасувати глушіння цього користувача?"
|
unsilenceConfirm: "Ви впевнені, що хочете скасувати глушіння цього користувача?"
|
||||||
|
popularUsers: "Популярні користувачі"
|
||||||
|
recentlyUpdatedUsers: "Нещодавно активні користувачі"
|
||||||
|
recentlyRegisteredUsers: "Нещодавно зареєстровані користувачі"
|
||||||
|
recentlyDiscoveredUsers: "Нещодавно знайдені користувачі"
|
||||||
|
popularTags: "Популярні теги"
|
||||||
userList: "Списки"
|
userList: "Списки"
|
||||||
aboutMisskey: "Про FoundKey"
|
aboutMisskey: "Про FoundKey"
|
||||||
administrator: "Адмін"
|
administrator: "Адмін"
|
||||||
|
@ -377,6 +389,7 @@ messagingWithGroup: "Чат з групою"
|
||||||
title: "Тема"
|
title: "Тема"
|
||||||
text: "Текст"
|
text: "Текст"
|
||||||
enable: "Увімкнути"
|
enable: "Увімкнути"
|
||||||
|
next: "Далі"
|
||||||
retype: "Введіть ще раз"
|
retype: "Введіть ще раз"
|
||||||
noteOf: "Нотатка {user}"
|
noteOf: "Нотатка {user}"
|
||||||
inviteToGroup: "Запрошення до групи"
|
inviteToGroup: "Запрошення до групи"
|
||||||
|
@ -567,6 +580,7 @@ abuseReports: "Скарги"
|
||||||
reportAbuse: "Поскаржитись"
|
reportAbuse: "Поскаржитись"
|
||||||
reportAbuseOf: "Поскаржитись на {name}"
|
reportAbuseOf: "Поскаржитись на {name}"
|
||||||
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги."
|
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги."
|
||||||
|
abuseReported: "Дякуємо, вашу скаргу було відправлено."
|
||||||
reporter: "Репортер"
|
reporter: "Репортер"
|
||||||
reporteeOrigin: "Про кого повідомлено"
|
reporteeOrigin: "Про кого повідомлено"
|
||||||
reporterOrigin: "Хто повідомив"
|
reporterOrigin: "Хто повідомив"
|
||||||
|
@ -617,6 +631,7 @@ disableShowingAnimatedImages: "Не програвати анімовані зо
|
||||||
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть\
|
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть\
|
||||||
\ по посиланню в листі для підтвердження."
|
\ по посиланню в листі для підтвердження."
|
||||||
emailVerified: "Електронну пошту підтверджено."
|
emailVerified: "Електронну пошту підтверджено."
|
||||||
|
noteFavoritesCount: "Кількість улюблених нотаток"
|
||||||
pageLikesCount: "Кількість отриманих вподобань сторінки"
|
pageLikesCount: "Кількість отриманих вподобань сторінки"
|
||||||
pageLikedCount: "Кількість вподобаних сторінок"
|
pageLikedCount: "Кількість вподобаних сторінок"
|
||||||
contact: "Контакт"
|
contact: "Контакт"
|
||||||
|
@ -804,6 +819,40 @@ _time:
|
||||||
minute: "х"
|
minute: "х"
|
||||||
hour: "г"
|
hour: "г"
|
||||||
day: "д"
|
day: "д"
|
||||||
|
_tutorial:
|
||||||
|
title: "Як користуватись FoundKey"
|
||||||
|
step1_1: "Ласкаво просимо!"
|
||||||
|
step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів\
|
||||||
|
\ на яких ви підписані."
|
||||||
|
step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки\
|
||||||
|
\ і не підписані на інших."
|
||||||
|
step2_1: "Перш ніж зробити запис або підписатись на когось, спочатку заповніть свій\
|
||||||
|
\ обліковий запис."
|
||||||
|
step2_2: "Надання деякої інформації про себе дозволить іншим користувачам підписатись\
|
||||||
|
\ на вас."
|
||||||
|
step3_1: "Ви успішно налаштували свій обліковий запис?"
|
||||||
|
step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення\
|
||||||
|
\ олівця на екрані."
|
||||||
|
step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку\
|
||||||
|
\ у верхньому правому куті форми."
|
||||||
|
step3_4: "Не знаєте що написати? Спробуйте \"налаштовую свій msky\"!"
|
||||||
|
step4_1: "Ви розмістили свій перший запис?"
|
||||||
|
step4_2: "Ура! Ваш перший запис відображається на вашій стрічці подій."
|
||||||
|
step5_1: "Настав час оживити вашу стрічку подій підписавшись на інших користувачів."
|
||||||
|
step5_2: "{featured} показує популярні записи , а {explore} популярних користувачів\
|
||||||
|
\ з цього інстансу. Спробуйте підписатись на користувача, який вам сподобався!"
|
||||||
|
step5_3: "Щоб підписатись на інших користувачів, нажміть на їхнє зображення, а потім\
|
||||||
|
\ на кнопку \"підписатись\"."
|
||||||
|
step5_4: "Якщо користувач має замок при імені, то йому потрібно буде вручну підтвердити\
|
||||||
|
\ вашу заявку на підписку."
|
||||||
|
step6_1: "Тепер ви повинні бачити записи інших користувачів на вашій стрічці подій."
|
||||||
|
step6_2: "Також ви можете швидко відповісти, або \"відреагувати\" на записи інших\
|
||||||
|
\ користувачів."
|
||||||
|
step6_3: "Щоб \"відреагувати\", нажміть на знак плюс \"+\" на записі і виберіть\
|
||||||
|
\ емоджі яким ви хочете \"відреагувати\"."
|
||||||
|
step7_1: "Вітаю! Ви пройшли ознайомлення з FoundKey."
|
||||||
|
step7_2: "Якщо ви хочете більше дізнатись про FoundKey, зайдіть в розділ {help}."
|
||||||
|
step7_3: "Насолоджуйтесь FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
registerKey: "Зареєструвати новий ключ безпеки"
|
registerKey: "Зареєструвати новий ключ безпеки"
|
||||||
_permissions:
|
_permissions:
|
||||||
|
|
|
@ -32,6 +32,9 @@ signup: "Đăng ký"
|
||||||
save: "Lưu"
|
save: "Lưu"
|
||||||
users: "Người dùng"
|
users: "Người dùng"
|
||||||
addUser: "Thêm người dùng"
|
addUser: "Thêm người dùng"
|
||||||
|
favorite: "Thêm vào yêu thích"
|
||||||
|
favorites: "Lượt thích"
|
||||||
|
unfavorite: "Bỏ thích"
|
||||||
pin: "Ghim"
|
pin: "Ghim"
|
||||||
unpin: "Bỏ ghim"
|
unpin: "Bỏ ghim"
|
||||||
copyContent: "Chép nội dung"
|
copyContent: "Chép nội dung"
|
||||||
|
@ -230,6 +233,7 @@ uploadFromUrl: "Tải lên bằng một URL"
|
||||||
uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên"
|
uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên"
|
||||||
uploadFromUrlRequested: "Đã yêu cầu tải lên"
|
uploadFromUrlRequested: "Đã yêu cầu tải lên"
|
||||||
uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong."
|
uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong."
|
||||||
|
explore: "Khám phá"
|
||||||
messageRead: "Đã đọc"
|
messageRead: "Đã đọc"
|
||||||
noMoreHistory: "Không còn gì để đọc"
|
noMoreHistory: "Không còn gì để đọc"
|
||||||
startMessaging: "Bắt đầu trò chuyện"
|
startMessaging: "Bắt đầu trò chuyện"
|
||||||
|
@ -311,6 +315,9 @@ inMb: "Tính bằng MB"
|
||||||
iconUrl: "URL Icon"
|
iconUrl: "URL Icon"
|
||||||
bannerUrl: "URL Ảnh bìa"
|
bannerUrl: "URL Ảnh bìa"
|
||||||
backgroundImageUrl: "URL Ảnh nền"
|
backgroundImageUrl: "URL Ảnh nền"
|
||||||
|
pinnedUsers: "Những người thú vị"
|
||||||
|
pinnedUsersDescription: "Liệt kê mỗi hàng một tên người dùng xuống dòng để ghim trên\
|
||||||
|
\ tab \"Khám phá\"."
|
||||||
hcaptchaSiteKey: "Khóa của trang"
|
hcaptchaSiteKey: "Khóa của trang"
|
||||||
hcaptchaSecretKey: "Khóa bí mật"
|
hcaptchaSecretKey: "Khóa bí mật"
|
||||||
recaptchaSiteKey: "Khóa của trang"
|
recaptchaSiteKey: "Khóa của trang"
|
||||||
|
@ -335,6 +342,11 @@ silence: "Ẩn"
|
||||||
silenceConfirm: "Bạn có chắc muốn ẩn người này?"
|
silenceConfirm: "Bạn có chắc muốn ẩn người này?"
|
||||||
unsilence: "Bỏ ẩn"
|
unsilence: "Bỏ ẩn"
|
||||||
unsilenceConfirm: "Bạn có chắc muốn bỏ ẩn người này?"
|
unsilenceConfirm: "Bạn có chắc muốn bỏ ẩn người này?"
|
||||||
|
popularUsers: "Những người nổi tiếng"
|
||||||
|
recentlyUpdatedUsers: "Hoạt động gần đây"
|
||||||
|
recentlyRegisteredUsers: "Mới tham gia"
|
||||||
|
recentlyDiscoveredUsers: "Mới khám phá"
|
||||||
|
popularTags: "Hashtag thông dụng"
|
||||||
userList: "Danh sách"
|
userList: "Danh sách"
|
||||||
aboutMisskey: "Về FoundKey"
|
aboutMisskey: "Về FoundKey"
|
||||||
administrator: "Quản trị viên"
|
administrator: "Quản trị viên"
|
||||||
|
@ -375,6 +387,7 @@ messagingWithGroup: "Chat nhóm"
|
||||||
title: "Tựa đề"
|
title: "Tựa đề"
|
||||||
text: "Nội dung"
|
text: "Nội dung"
|
||||||
enable: "Bật"
|
enable: "Bật"
|
||||||
|
next: "Kế tiếp"
|
||||||
retype: "Nhập lại"
|
retype: "Nhập lại"
|
||||||
noteOf: "Tút của {user}"
|
noteOf: "Tút của {user}"
|
||||||
inviteToGroup: "Mời vào nhóm"
|
inviteToGroup: "Mời vào nhóm"
|
||||||
|
@ -565,6 +578,7 @@ abuseReports: "Lượt báo cáo"
|
||||||
reportAbuse: "Báo cáo"
|
reportAbuse: "Báo cáo"
|
||||||
reportAbuseOf: "Báo cáo {name}"
|
reportAbuseOf: "Báo cáo {name}"
|
||||||
fillAbuseReportDescription: "Vui lòng điền thông tin chi tiết về báo cáo này."
|
fillAbuseReportDescription: "Vui lòng điền thông tin chi tiết về báo cáo này."
|
||||||
|
abuseReported: "Báo cáo đã được gửi. Cảm ơn bạn nhiều."
|
||||||
reporter: "Người báo cáo"
|
reporter: "Người báo cáo"
|
||||||
reporteeOrigin: "Bị báo cáo"
|
reporteeOrigin: "Bị báo cáo"
|
||||||
reporterOrigin: "Máy chủ người báo cáo"
|
reporterOrigin: "Máy chủ người báo cáo"
|
||||||
|
@ -614,6 +628,7 @@ disableShowingAnimatedImages: "Không phát ảnh động"
|
||||||
verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết\
|
verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết\
|
||||||
\ đính kèm để hoàn tất xác minh."
|
\ đính kèm để hoàn tất xác minh."
|
||||||
emailVerified: "Email đã được xác minh"
|
emailVerified: "Email đã được xác minh"
|
||||||
|
noteFavoritesCount: "Số lượng tút yêu thích"
|
||||||
pageLikesCount: "Số lượng trang đã thích"
|
pageLikesCount: "Số lượng trang đã thích"
|
||||||
pageLikedCount: "Số lượng thích trang đã nhận"
|
pageLikedCount: "Số lượng thích trang đã nhận"
|
||||||
contact: "Liên hệ"
|
contact: "Liên hệ"
|
||||||
|
@ -955,6 +970,43 @@ _time:
|
||||||
minute: "phút"
|
minute: "phút"
|
||||||
hour: "giờ"
|
hour: "giờ"
|
||||||
day: "ngày"
|
day: "ngày"
|
||||||
|
_tutorial:
|
||||||
|
title: "Cách dùng FoundKey"
|
||||||
|
step1_1: "Xin chào!"
|
||||||
|
step1_2: "Trang này gọi là \"bảng tin\". Nó hiện \"tút\" từ những người mà bạn \"\
|
||||||
|
theo dõi\" theo thứ tự thời gian."
|
||||||
|
step1_3: "Bảng tin của bạn đang trống, bởi vì bạn chưa đăng tút nào hoặc chưa theo\
|
||||||
|
\ dõi ai."
|
||||||
|
step2_1: "Hãy hoàn thành việc thiết lập hồ sơ của bạn trước khi viết tút hoặc theo\
|
||||||
|
\ dõi bất kỳ ai."
|
||||||
|
step2_2: "Cung cấp một số thông tin giới thiệu bạn là ai sẽ giúp người khác dễ dàng\
|
||||||
|
\ biết được họ muốn đọc tút hay theo dõi bạn."
|
||||||
|
step3_1: "Hoàn thành thiết lập hồ sơ của bạn?"
|
||||||
|
step3_2: "Sau đó, hãy thử đăng một tút tiếp theo. Bạn có thể làm như vậy bằng cách\
|
||||||
|
\ nhấn vào nút có biểu tượng bút chì trên màn hình."
|
||||||
|
step3_3: "Nhập nội dung vào khung soạn thảo và nhấn nút đăng ở góc trên."
|
||||||
|
step3_4: "Chưa biết nói gì? Thử \"Tôi mới tham gia FoundKey\"!"
|
||||||
|
step4_1: "Đăng xong tút đầu tiên của bạn?"
|
||||||
|
step4_2: "De! Tút đầu tiên của bạn đã hiện trên bảng tin."
|
||||||
|
step5_1: "Bây giờ, hãy thử làm cho bảng tin của bạn sinh động hơn bằng cách theo\
|
||||||
|
\ dõi những người khác."
|
||||||
|
step5_2: "{feature} sẽ hiển thị cho bạn các tút nổi bật trên máy chủ này. {explore}\
|
||||||
|
\ sẽ cho phép bạn tìm thấy những người dùng thú vị. Hãy thử tìm những người bạn\
|
||||||
|
\ muốn theo dõi ở đó!"
|
||||||
|
step5_3: "Để theo dõi những người dùng khác, hãy nhấn vào ảnh đại diện của họ và\
|
||||||
|
\ nhấn nút \"Theo dõi\" trên hồ sơ của họ."
|
||||||
|
step5_4: "Nếu người dùng khác có biểu tượng ổ khóa bên cạnh tên của họ, có thể mất\
|
||||||
|
\ một khoảng thời gian để người dùng đó phê duyệt yêu cầu theo dõi của bạn theo\
|
||||||
|
\ cách thủ công."
|
||||||
|
step6_1: "Bạn sẽ có thể xem tút của những người dùng khác trên bảng tin của mình\
|
||||||
|
\ ngay bây giờ."
|
||||||
|
step6_2: "Bạn cũng có thể đặt \"biểu cảm\" trên tút của người khác để phản hồi nhanh\
|
||||||
|
\ chúng."
|
||||||
|
step6_3: "Để đính kèm \"biểu cảm\", hãy nhấn vào dấu \"+\" trên tút của người dùng\
|
||||||
|
\ khác rồi chọn biểu tượng cảm xúc mà bạn muốn dùng."
|
||||||
|
step7_1: "Xin chúc mừng! Bây giờ bạn đã hoàn thành phần hướng dẫn cơ bản của FoundKey."
|
||||||
|
step7_2: "Nếu bạn muốn tìm hiểu thêm về FoundKey, hãy thử phần {help}."
|
||||||
|
step7_3: "Bây giờ, chúc may mắn và vui vẻ với FoundKey! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước."
|
alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước."
|
||||||
registerDevice: "Đăng ký một thiết bị"
|
registerDevice: "Đăng ký một thiết bị"
|
||||||
|
|
|
@ -30,6 +30,9 @@ signup: "新用户注册"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
users: "用户"
|
users: "用户"
|
||||||
addUser: "添加用户"
|
addUser: "添加用户"
|
||||||
|
favorite: "收藏"
|
||||||
|
favorites: "收藏"
|
||||||
|
unfavorite: "取消收藏"
|
||||||
pin: "置顶"
|
pin: "置顶"
|
||||||
unpin: "取消置顶"
|
unpin: "取消置顶"
|
||||||
copyContent: "复制内容"
|
copyContent: "复制内容"
|
||||||
|
@ -211,6 +214,7 @@ uploadFromUrl: "从网址上传"
|
||||||
uploadFromUrlDescription: "输入文件的URL"
|
uploadFromUrlDescription: "输入文件的URL"
|
||||||
uploadFromUrlRequested: "请求上传"
|
uploadFromUrlRequested: "请求上传"
|
||||||
uploadFromUrlMayTakeTime: "上传可能需要一些时间完成。"
|
uploadFromUrlMayTakeTime: "上传可能需要一些时间完成。"
|
||||||
|
explore: "发现"
|
||||||
messageRead: "已读"
|
messageRead: "已读"
|
||||||
noMoreHistory: "没有更多的历史记录"
|
noMoreHistory: "没有更多的历史记录"
|
||||||
startMessaging: "添加聊天"
|
startMessaging: "添加聊天"
|
||||||
|
@ -289,6 +293,8 @@ inMb: "以兆字节(MegaByte)为单位"
|
||||||
iconUrl: "图标URL"
|
iconUrl: "图标URL"
|
||||||
bannerUrl: "横幅URL"
|
bannerUrl: "横幅URL"
|
||||||
backgroundImageUrl: "背景图URL"
|
backgroundImageUrl: "背景图URL"
|
||||||
|
pinnedUsers: "置顶用户"
|
||||||
|
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
|
||||||
hcaptchaSiteKey: "网站密钥"
|
hcaptchaSiteKey: "网站密钥"
|
||||||
hcaptchaSecretKey: "密钥"
|
hcaptchaSecretKey: "密钥"
|
||||||
recaptchaSiteKey: "网站密钥"
|
recaptchaSiteKey: "网站密钥"
|
||||||
|
@ -312,6 +318,11 @@ silence: "禁言"
|
||||||
silenceConfirm: "确认要禁言吗?"
|
silenceConfirm: "确认要禁言吗?"
|
||||||
unsilence: "解除禁言"
|
unsilence: "解除禁言"
|
||||||
unsilenceConfirm: "要解除禁言吗?"
|
unsilenceConfirm: "要解除禁言吗?"
|
||||||
|
popularUsers: "热门用户"
|
||||||
|
recentlyUpdatedUsers: "最近投稿的用户"
|
||||||
|
recentlyRegisteredUsers: "最近登录的用户"
|
||||||
|
recentlyDiscoveredUsers: "最近发现的用户"
|
||||||
|
popularTags: "热门标签"
|
||||||
userList: "列表"
|
userList: "列表"
|
||||||
aboutMisskey: "关于 FoundKey"
|
aboutMisskey: "关于 FoundKey"
|
||||||
administrator: "管理员"
|
administrator: "管理员"
|
||||||
|
@ -352,6 +363,7 @@ messagingWithGroup: "与群组聊天"
|
||||||
title: "标题"
|
title: "标题"
|
||||||
text: "文本"
|
text: "文本"
|
||||||
enable: "启用"
|
enable: "启用"
|
||||||
|
next: "下一个"
|
||||||
retype: "重新输入"
|
retype: "重新输入"
|
||||||
noteOf: "{user}的帖子"
|
noteOf: "{user}的帖子"
|
||||||
inviteToGroup: "群组邀请"
|
inviteToGroup: "群组邀请"
|
||||||
|
@ -526,6 +538,7 @@ abuseReports: "举报"
|
||||||
reportAbuse: "举报"
|
reportAbuse: "举报"
|
||||||
reportAbuseOf: "举报{name}"
|
reportAbuseOf: "举报{name}"
|
||||||
fillAbuseReportDescription: "请填写举报的详细原因。"
|
fillAbuseReportDescription: "请填写举报的详细原因。"
|
||||||
|
abuseReported: "内容已发送。感谢您提交信息。"
|
||||||
reporter: "举报者"
|
reporter: "举报者"
|
||||||
reporteeOrigin: "举报来源"
|
reporteeOrigin: "举报来源"
|
||||||
reporterOrigin: "举报者来源"
|
reporterOrigin: "举报者来源"
|
||||||
|
@ -570,6 +583,7 @@ loadRawImages: "添加附件图像的缩略图时使用原始图像质量"
|
||||||
disableShowingAnimatedImages: "不播放动画"
|
disableShowingAnimatedImages: "不播放动画"
|
||||||
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
|
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
|
||||||
emailVerified: "电子邮件地址已验证"
|
emailVerified: "电子邮件地址已验证"
|
||||||
|
noteFavoritesCount: "收藏的帖子数"
|
||||||
pageLikesCount: "页面点赞次数"
|
pageLikesCount: "页面点赞次数"
|
||||||
pageLikedCount: "页面被点赞次数"
|
pageLikedCount: "页面被点赞次数"
|
||||||
contact: "联系人"
|
contact: "联系人"
|
||||||
|
@ -887,6 +901,29 @@ _time:
|
||||||
minute: "分"
|
minute: "分"
|
||||||
hour: "小时"
|
hour: "小时"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
_tutorial:
|
||||||
|
title: "FoundKey的使用方法"
|
||||||
|
step1_1: "欢迎!"
|
||||||
|
step1_2: "这个页面叫做「时间线」,它会按照时间顺序显示所有你「关注」的人所发的「帖子」。"
|
||||||
|
step1_3: "如果你并没有发布任何帖子,也没有关注其他的人,你的时间线页面应当什么都没有显示。"
|
||||||
|
step2_1: "在您想要发帖或关注其他人之前,请先设置一下个人资料吧。"
|
||||||
|
step2_2: "如果别人能够更加的了解你,关注你的概率也会得到提升。"
|
||||||
|
step3_1: "已经设置完个人资料了吗?"
|
||||||
|
step3_2: "那么接下来,试着写一些什么东西来发布吧。你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
|
||||||
|
step3_3: "写完内容后,点击窗口右上方的按钮就可以投稿。"
|
||||||
|
step3_4: "不知道说些什么好吗?那就写下「FoundKey我来啦!」这样的话吧。"
|
||||||
|
step4_1: "将你的话语发布出去了吗?"
|
||||||
|
step4_2: "太棒了!现在你可以在你的时间线中看到你刚刚发布的帖子了。"
|
||||||
|
step5_1: "接下来,关注其他人来使时间线更生动吧。"
|
||||||
|
step5_2: "{featured}将向您展示热门趋势的帖子。 {explore}将让您找到热门用户。 尝试关注您喜欢的人!"
|
||||||
|
step5_3: "要关注其他用户,请单击他的头像,然后在他的个人资料上按下“关注”按钮。"
|
||||||
|
step5_4: "如果用户的名称旁边有锁定图标,则该用户需要手动批准您的关注请求。"
|
||||||
|
step6_1: "现在,您将可以在时间线上看到其他用户的帖子。"
|
||||||
|
step6_2: "您还可以在其他人的帖子上进行「回应」,以快速做出简单回复。"
|
||||||
|
step6_3: "在他人的贴子上按下「+」图标,即可选择想要的表情来进行「回应」。"
|
||||||
|
step7_1: "对FoundKey基本操作的简单介绍,就到此结束了。 辛苦了!"
|
||||||
|
step7_2: "如果你想了解更多有关FoundKey的信息,请参见{help}。"
|
||||||
|
step7_3: "接下来,享受FoundKey带来的乐趣吧\U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此设备已被注册"
|
alreadyRegistered: "此设备已被注册"
|
||||||
registerDevice: "注册设备"
|
registerDevice: "注册设备"
|
||||||
|
|
|
@ -30,6 +30,9 @@ signup: "註冊"
|
||||||
save: "儲存"
|
save: "儲存"
|
||||||
users: "使用者"
|
users: "使用者"
|
||||||
addUser: "新增使用者"
|
addUser: "新增使用者"
|
||||||
|
favorite: "我的最愛"
|
||||||
|
favorites: "我的最愛"
|
||||||
|
unfavorite: "從我的最愛中移除"
|
||||||
pin: "置頂"
|
pin: "置頂"
|
||||||
unpin: "取消置頂"
|
unpin: "取消置頂"
|
||||||
copyContent: "複製內容"
|
copyContent: "複製內容"
|
||||||
|
@ -211,6 +214,7 @@ uploadFromUrl: "從網址上傳"
|
||||||
uploadFromUrlDescription: "您要上傳的文件的URL"
|
uploadFromUrlDescription: "您要上傳的文件的URL"
|
||||||
uploadFromUrlRequested: "已請求上傳"
|
uploadFromUrlRequested: "已請求上傳"
|
||||||
uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
|
uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
|
||||||
|
explore: "探索"
|
||||||
messageRead: "已讀"
|
messageRead: "已讀"
|
||||||
noMoreHistory: "沒有更多歷史紀錄"
|
noMoreHistory: "沒有更多歷史紀錄"
|
||||||
startMessaging: "開始傳送訊息"
|
startMessaging: "開始傳送訊息"
|
||||||
|
@ -289,6 +293,8 @@ inMb: "以Mbps為單位"
|
||||||
iconUrl: "圖像URL"
|
iconUrl: "圖像URL"
|
||||||
bannerUrl: "橫幅圖像URL"
|
bannerUrl: "橫幅圖像URL"
|
||||||
backgroundImageUrl: "背景圖片的來源網址"
|
backgroundImageUrl: "背景圖片的來源網址"
|
||||||
|
pinnedUsers: "置頂用戶"
|
||||||
|
pinnedUsersDescription: "在「發現」頁面中使用換行標記想要置頂的使用者。"
|
||||||
hcaptchaSiteKey: "網站金鑰"
|
hcaptchaSiteKey: "網站金鑰"
|
||||||
hcaptchaSecretKey: "金鑰"
|
hcaptchaSecretKey: "金鑰"
|
||||||
recaptchaSiteKey: "網站金鑰"
|
recaptchaSiteKey: "網站金鑰"
|
||||||
|
@ -312,6 +318,11 @@ silence: "禁言"
|
||||||
silenceConfirm: "確定要禁言此用戶嗎?"
|
silenceConfirm: "確定要禁言此用戶嗎?"
|
||||||
unsilence: "解除禁言"
|
unsilence: "解除禁言"
|
||||||
unsilenceConfirm: "確定要解除禁言嗎?"
|
unsilenceConfirm: "確定要解除禁言嗎?"
|
||||||
|
popularUsers: "熱門使用者"
|
||||||
|
recentlyUpdatedUsers: "最近發文的使用者"
|
||||||
|
recentlyRegisteredUsers: "新加入使用者"
|
||||||
|
recentlyDiscoveredUsers: "最近發現的使用者"
|
||||||
|
popularTags: "熱門標籤"
|
||||||
userList: "清單"
|
userList: "清單"
|
||||||
aboutMisskey: "關於 FoundKey"
|
aboutMisskey: "關於 FoundKey"
|
||||||
administrator: "管理員"
|
administrator: "管理員"
|
||||||
|
@ -352,6 +363,7 @@ messagingWithGroup: "發送訊息至群組"
|
||||||
title: "標題"
|
title: "標題"
|
||||||
text: "文字"
|
text: "文字"
|
||||||
enable: "啟用"
|
enable: "啟用"
|
||||||
|
next: "下一步"
|
||||||
retype: "重新輸入"
|
retype: "重新輸入"
|
||||||
noteOf: "{user}的貼文"
|
noteOf: "{user}的貼文"
|
||||||
inviteToGroup: "邀請至群組"
|
inviteToGroup: "邀請至群組"
|
||||||
|
@ -525,6 +537,7 @@ abuseReports: "檢舉"
|
||||||
reportAbuse: "檢舉"
|
reportAbuse: "檢舉"
|
||||||
reportAbuseOf: "檢舉{name}"
|
reportAbuseOf: "檢舉{name}"
|
||||||
fillAbuseReportDescription: "請填寫檢舉的詳細理由。"
|
fillAbuseReportDescription: "請填寫檢舉的詳細理由。"
|
||||||
|
abuseReported: "回報已送出。感謝您的報告。"
|
||||||
reporter: "檢舉者"
|
reporter: "檢舉者"
|
||||||
reporteeOrigin: "檢舉來源"
|
reporteeOrigin: "檢舉來源"
|
||||||
reporterOrigin: "檢舉者來源"
|
reporterOrigin: "檢舉者來源"
|
||||||
|
@ -569,6 +582,7 @@ loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
|
||||||
disableShowingAnimatedImages: "不播放動態圖檔"
|
disableShowingAnimatedImages: "不播放動態圖檔"
|
||||||
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
|
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
|
||||||
emailVerified: "已成功驗證您的電郵"
|
emailVerified: "已成功驗證您的電郵"
|
||||||
|
noteFavoritesCount: "我的最愛貼文的數目"
|
||||||
pageLikesCount: "頁面被按讚次數"
|
pageLikesCount: "頁面被按讚次數"
|
||||||
pageLikedCount: "頁面被按讚次數"
|
pageLikedCount: "頁面被按讚次數"
|
||||||
contact: "聯絡人"
|
contact: "聯絡人"
|
||||||
|
@ -886,6 +900,29 @@ _time:
|
||||||
minute: "分鐘"
|
minute: "分鐘"
|
||||||
hour: "小時"
|
hour: "小時"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
_tutorial:
|
||||||
|
title: "FoundKey使用方法"
|
||||||
|
step1_1: "歡迎!"
|
||||||
|
step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」"
|
||||||
|
step1_3: "由於你沒有發佈任何貼文,也沒有追隨任何人,所以你的時間軸目前是空的。"
|
||||||
|
step2_1: "在發文或追隨其他人之前先讓我們設定一下個人資料吧。"
|
||||||
|
step2_2: "提供一些關於自己的資訊來讓其他人更有追隨你的意願。"
|
||||||
|
step3_1: "個人資料都設定好了嗎?"
|
||||||
|
step3_2: "接下來,讓我們來試試看發個文,按一下畫面上的鉛筆圖示來開始"
|
||||||
|
step3_3: "輸入完內容後,按視窗右上角的按鈕來發文"
|
||||||
|
step3_4: "不知道該寫什麼內容嗎?試試看「開始使用FoundKey了」如何。"
|
||||||
|
step4_1: "貼文發出去了嗎?"
|
||||||
|
step4_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
|
||||||
|
step5_1: "現在試試看追隨其他人來讓你的時間軸變得更生動吧。"
|
||||||
|
step5_2: "你會在{featured}上看到受歡迎的貼文,你也可以從列表中追隨你喜歡的人,或者在{explore}上找到熱門使用者。"
|
||||||
|
step5_3: "想要追隨其他人,只要點擊他們的大頭貼並按「追隨」即可。"
|
||||||
|
step5_4: "如果使用者的名字旁有鎖頭的圖示,代表他們需要手動核准你的追隨請求。"
|
||||||
|
step6_1: "現在你可以在時間軸上看到其他用戶的貼文。"
|
||||||
|
step6_2: "你也可以對別人的貼文作出「情感」,作出簡單的回覆。"
|
||||||
|
step6_3: "在他人的貼文按下\"+\"圖標,即可選擇喜好的表情符號進行回應。"
|
||||||
|
step7_1: "以上為FoundKey的基本操作說明,教學在此告一段落。辛苦了。"
|
||||||
|
step7_2: "歡迎到{help}來瞭解更多FoundKey相關介紹。"
|
||||||
|
step7_3: "那麼,祝您在FoundKey玩的開心~ \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此設備已經被註冊過了"
|
alreadyRegistered: "此設備已經被註冊過了"
|
||||||
registerDevice: "註冊裝置"
|
registerDevice: "註冊裝置"
|
||||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "foundkey",
|
"name": "foundkey",
|
||||||
"version": "13.0.0-preview6",
|
"version": "13.0.0-preview5",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://akkoma.dev/FoundKeyGang/FoundKey.git"
|
"url": "https://akkoma.dev/FoundKeyGang/FoundKey.git"
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
"build": "yarn workspaces foreach --topological run build && yarn run gulp",
|
"build": "yarn workspaces foreach --topological run build && yarn run gulp",
|
||||||
"build-parallel": "yarn workspaces foreach --parallel --topological run build && yarn run gulp",
|
"build-parallel": "yarn workspaces foreach --parallel --topological run build && yarn run gulp",
|
||||||
"start": "yarn workspace backend run start",
|
"start": "yarn workspace backend run start",
|
||||||
|
"start:test": "yarn workspace backend run start:test",
|
||||||
"init": "yarn migrate",
|
"init": "yarn migrate",
|
||||||
"migrate": "yarn workspace backend run migrate",
|
"migrate": "yarn workspace backend run migrate",
|
||||||
"migrateandstart": "yarn migrate && yarn start",
|
"migrateandstart": "yarn migrate && yarn start",
|
||||||
|
@ -20,6 +21,11 @@
|
||||||
"watch": "yarn dev",
|
"watch": "yarn dev",
|
||||||
"dev": "node ./scripts/dev.mjs",
|
"dev": "node ./scripts/dev.mjs",
|
||||||
"lint": "yarn workspaces foreach run lint",
|
"lint": "yarn workspaces foreach run lint",
|
||||||
|
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||||
|
"cy:run": "cypress run",
|
||||||
|
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
|
||||||
|
"mocha": "yarn workspace backend run mocha",
|
||||||
|
"test": "yarn mocha",
|
||||||
"format": "gulp format",
|
"format": "gulp format",
|
||||||
"clean": "node ./scripts/clean.mjs",
|
"clean": "node ./scripts/clean.mjs",
|
||||||
"clean-all": "node ./scripts/clean-all.mjs",
|
"clean-all": "node ./scripts/clean-all.mjs",
|
||||||
|
@ -30,6 +36,7 @@
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"argon2": "^0.30.2",
|
||||||
"execa": "5.1.1",
|
"execa": "5.1.1",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-cssnano": "2.1.3",
|
"gulp-cssnano": "2.1.3",
|
||||||
|
@ -43,6 +50,8 @@
|
||||||
"@types/gulp-rename": "2.0.1",
|
"@types/gulp-rename": "2.0.1",
|
||||||
"@typescript-eslint/parser": "^5.46.1",
|
"@typescript-eslint/parser": "^5.46.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
"cypress": "10.3.0",
|
||||||
|
"start-server-and-test": "1.14.0",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.4.1"
|
"packageManager": "yarn@3.4.1"
|
||||||
|
|
10
packages/backend/.mocharc.json
Normal file
10
packages/backend/.mocharc.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extension": ["ts","js","cjs","mjs"],
|
||||||
|
"node-option": [
|
||||||
|
"experimental-specifier-resolution=node",
|
||||||
|
"loader=./test/loader.js"
|
||||||
|
],
|
||||||
|
"slow": 1000,
|
||||||
|
"timeout": 30000,
|
||||||
|
"exit": true
|
||||||
|
}
|
|
@ -7,14 +7,6 @@ export class removeMentionedRemoteUsersColumn1661376843000 {
|
||||||
|
|
||||||
async down(queryRunner) {
|
async down(queryRunner) {
|
||||||
await queryRunner.query(`ALTER TABLE "note" ADD "mentionedRemoteUsers" TEXT NOT NULL DEFAULT '[]'::text`);
|
await queryRunner.query(`ALTER TABLE "note" ADD "mentionedRemoteUsers" TEXT NOT NULL DEFAULT '[]'::text`);
|
||||||
await queryRunner.query(`CREATE TEMP TABLE IF NOT EXISTS "temp_mentions" AS
|
await queryRunner.query(`UPDATE "note" SET "mentionedRemoteUsers" = (SELECT COALESCE(json_agg(row_to_json("data"))::text, '[]') FROM (SELECT "url", "uri", "username", "host" FROM "user" JOIN "user_profile" ON "user"."id" = "user_profile". "userId" WHERE "user"."host" IS NOT NULL AND "user"."id" = ANY("note"."mentions")) AS "data")`);
|
||||||
SELECT "id", "url", "uri", "username", "host"
|
|
||||||
FROM "user"
|
|
||||||
JOIN "user_profile" ON "user"."id" = "user_profile". "userId" WHERE "user"."host" IS NOT NULL`);
|
|
||||||
await queryRunner.query(`CREATE UNIQUE INDEX "temp_mentions_id" ON "temp_mentions"("id")`);
|
|
||||||
await queryRunner.query(`UPDATE "note" SET "mentionedRemoteUsers" = (
|
|
||||||
SELECT COALESCE(json_agg(row_to_json("data")::jsonb - 'id')::text, '[]') FROM "temp_mentions" AS "data"
|
|
||||||
WHERE "data"."id" = ANY("note"."mentions")
|
|
||||||
)`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
export class noteEditing1685997617959 {
|
|
||||||
name = 'noteEditing1685997617959';
|
|
||||||
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
|
|
||||||
await queryRunner.query(`COMMENT ON COLUMN "note"."updatedAt" IS 'The updated date of the Note.'`);
|
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app', 'updated')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app', 'updated')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('move', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'pollEnded')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('move', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
|
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
export class syncDatabase1689005520053 {
|
|
||||||
name = 'syncDatabase1689005520053';
|
|
||||||
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isDeleted" IS 'How many delivery jobs are outstanding before the deletion is completed.'`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."note_thread_muting_mutingnotificationtypes_enum" RENAME TO "note_thread_muting_mutingnotificationtypes_enum_old"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."note_thread_muting_mutingnotificationtypes_enum" AS ENUM('mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'update')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."note_thread_muting_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."note_thread_muting_mutingnotificationtypes_enum"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."note_thread_muting_mutingnotificationtypes_enum_old"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'update', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'update', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app', 'updated')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'move', 'app', 'updated')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."note_thread_muting_mutingnotificationtypes_enum_old" AS ENUM('mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."note_thread_muting_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."note_thread_muting_mutingnotificationtypes_enum_old"[]`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."note_thread_muting_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`ALTER TYPE "public"."note_thread_muting_mutingnotificationtypes_enum_old" RENAME TO "note_thread_muting_mutingnotificationtypes_enum"`);
|
|
||||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isDeleted" IS NULL`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
export class removePinnedUsers1704234742539 {
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedUsers"`);
|
|
||||||
}
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
export class removeNoteVisibility1704236065406 {
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "poll" DROP COLUMN "noteVisibility"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "poll_notevisibility_enum"`);
|
|
||||||
}
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`CREATE TYPE "poll_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "poll" ADD "noteVisibility" "poll_notevisibility_enum" NOT NULL`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
export class removeHashtagChart1710687673333 {
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`DROP TABLE "__chart__hashtag"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "__chart_day__hashtag"`);
|
|
||||||
}
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`CREATE TABLE public.__chart__hashtag ("id" SERIAL NOT NULL CONSTRAINT "PK_c32f1ea2b44a5d2f7881e37f8f9" PRIMARY KEY,"date" integer NOT NULL,"group" character varying(128) NOT NULL,"___local_users" integer DEFAULT 0 NOT NULL,"___remote_users" integer DEFAULT 0 NOT NULL,"unique_temp___local_users" character varying[] DEFAULT '{}'::character varying[] NOT NULL,"unique_temp___remote_users" character varying[] DEFAULT '{}'::character varying[] NOT NULL,CONSTRAINT "UQ_25a97c02003338124b2b75fdbc8" UNIQUE ("date", "group"))`);
|
|
||||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON public.__chart__hashtag USING btree (date, "group")`);
|
|
||||||
await queryRunner.query(`CREATE TABLE public.__chart_day__hashtag ("id" SERIAL NOT NULL CONSTRAINT CONSTRAINT "PK_13d5a3b089344e5557f8e0980b4" PRIMARY KEY,"date" integer NOT NULL,"group" character varying(128) NOT NULL,"___local_users" integer DEFAULT 0 NOT NULL,"___remote_users" integer DEFAULT 0 NOT NULL,"unique_temp___local_users" character varying[] DEFAULT '{}'::character varying[] NOT NULL,"unique_temp___remote_users" character varying[] DEFAULT '{}'::character varying[] NOT NULL,CONSTRAINT "UQ_8f589cf056ff51f09d6096f6450" UNIQUE ("date", "group"))`);
|
|
||||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8f589cf056ff51f09d6096f645" ON public.__chart_day__hashtag USING btree (date, "group")`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
export class removeSummaly1725695130950 {
|
|
||||||
name = 'removeSummaly1725695130950'
|
|
||||||
|
|
||||||
async up(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "summalyProxy"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async down(queryRunner) {
|
|
||||||
await queryRunner.query(`ALTER TABLE "meta" ADD "summalyProxy" character varying(128)`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "13.0.0-preview6",
|
"version": "13.0.0-preview5",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -8,29 +8,39 @@
|
||||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||||
"watch": "node watch.mjs",
|
"watch": "node watch.mjs",
|
||||||
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
||||||
"migrate": "yarn exec typeorm migration:run -d ormconfig.js",
|
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
||||||
"start": "node --experimental-json-modules ./built/index.js"
|
"migrate": "npx typeorm migration:run -d ormconfig.js",
|
||||||
|
"start": "node --experimental-json-modules ./built/index.js",
|
||||||
|
"start:test": "cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js",
|
||||||
|
"test": "npm run mocha"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "^4.3.1",
|
"@bull-board/api": "^4.3.1",
|
||||||
"@bull-board/koa": "^4.3.1",
|
"@bull-board/koa": "^4.3.1",
|
||||||
"@discordapp/twemoji": "14.0.2",
|
"@discordapp/twemoji": "14.0.2",
|
||||||
|
"@elastic/elasticsearch": "7.11.0",
|
||||||
"@koa/cors": "3.1.0",
|
"@koa/cors": "3.1.0",
|
||||||
"@koa/multer": "3.0.0",
|
"@koa/multer": "3.0.0",
|
||||||
"@koa/router": "9.0.1",
|
"@koa/router": "9.0.1",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
|
"@sinonjs/fake-timers": "9.1.2",
|
||||||
|
"@syuilo/aiscript": "0.11.1",
|
||||||
|
"abort-controller": "3.0.0",
|
||||||
"ajv": "8.11.0",
|
"ajv": "8.11.0",
|
||||||
"archiver": "5.3.1",
|
"archiver": "5.3.1",
|
||||||
"argon2": "^0.30.2",
|
"autobind-decorator": "2.4.0",
|
||||||
"aws-sdk": "2.1165.0",
|
"aws-sdk": "2.1165.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "1.1.5",
|
"blurhash": "1.1.5",
|
||||||
"bull": "4.8.4",
|
"bull": "4.8.4",
|
||||||
"cacheable-lookup": "6.0.4",
|
"cacheable-lookup": "6.0.4",
|
||||||
"cbor": "8.1.0",
|
"cbor": "8.1.0",
|
||||||
|
"chalk": "5.0.1",
|
||||||
|
"chalk-template": "0.4.0",
|
||||||
|
"cli-highlight": "2.1.11",
|
||||||
|
"color-convert": "2.0.1",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "2.28.0",
|
||||||
"decompress": "4.2.1",
|
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
|
@ -41,13 +51,13 @@
|
||||||
"hpagent": "0.1.2",
|
"hpagent": "0.1.2",
|
||||||
"ioredis": "4.28.5",
|
"ioredis": "4.28.5",
|
||||||
"ip-cidr": "3.0.10",
|
"ip-cidr": "3.0.10",
|
||||||
"ipaddr.js": "2.1.0",
|
|
||||||
"is-svg": "4.3.2",
|
"is-svg": "4.3.2",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsdom": "20.0.0",
|
"jsdom": "20.0.0",
|
||||||
|
"json5": "2.2.1",
|
||||||
|
"json5-loader": "4.0.1",
|
||||||
"jsonld": "6.0.0",
|
"jsonld": "6.0.0",
|
||||||
"jsrsasign": "10.5.25",
|
"jsrsasign": "10.5.25",
|
||||||
"katex": "^0.16.0",
|
|
||||||
"koa": "2.13.4",
|
"koa": "2.13.4",
|
||||||
"koa-bodyparser": "4.3.0",
|
"koa-bodyparser": "4.3.0",
|
||||||
"koa-favicon": "2.1.0",
|
"koa-favicon": "2.1.0",
|
||||||
|
@ -55,9 +65,11 @@
|
||||||
"koa-logger": "3.2.1",
|
"koa-logger": "3.2.1",
|
||||||
"koa-mount": "4.0.0",
|
"koa-mount": "4.0.0",
|
||||||
"koa-send": "5.0.1",
|
"koa-send": "5.0.1",
|
||||||
|
"koa-slow": "2.1.0",
|
||||||
"koa-views": "7.0.2",
|
"koa-views": "7.0.2",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
|
"mocha": "10.2.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.2.6",
|
"node-fetch": "3.2.6",
|
||||||
|
@ -65,6 +77,7 @@
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"parse5": "7.0.0",
|
"parse5": "7.0.0",
|
||||||
"pg": "8.7.3",
|
"pg": "8.7.3",
|
||||||
|
"private-ip": "2.3.3",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
"pug": "3.0.2",
|
"pug": "3.0.2",
|
||||||
|
@ -75,7 +88,9 @@
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.17.8",
|
"re2": "1.17.8",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
|
"reflect-metadata": "0.1.13",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
|
"require-all": "3.0.0",
|
||||||
"rss-parser": "3.12.0",
|
"rss-parser": "3.12.0",
|
||||||
"sanitize-html": "2.7.0",
|
"sanitize-html": "2.7.0",
|
||||||
"semver": "7.3.7",
|
"semver": "7.3.7",
|
||||||
|
@ -83,12 +98,18 @@
|
||||||
"speakeasy": "2.0.0",
|
"speakeasy": "2.0.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
|
"style-loader": "3.3.1",
|
||||||
|
"summaly": "2.7.0",
|
||||||
"systeminformation": "5.11.22",
|
"systeminformation": "5.11.22",
|
||||||
"tinycolor2": "1.4.2",
|
"tinycolor2": "1.4.2",
|
||||||
"tmp": "0.2.1",
|
"tmp": "0.2.1",
|
||||||
|
"ts-loader": "9.3.1",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
"tsc-alias": "1.7.0",
|
"tsc-alias": "1.7.0",
|
||||||
|
"tsconfig-paths": "4.1.0",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"typeorm": "0.3.7",
|
"typeorm": "0.3.7",
|
||||||
|
"unzipper": "0.10.11",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"web-push": "3.5.0",
|
"web-push": "3.5.0",
|
||||||
"ws": "8.8.0",
|
"ws": "8.8.0",
|
||||||
|
@ -118,6 +139,7 @@
|
||||||
"@types/koa__cors": "3.1.1",
|
"@types/koa__cors": "3.1.1",
|
||||||
"@types/koa__multer": "2.0.4",
|
"@types/koa__multer": "2.0.4",
|
||||||
"@types/koa__router": "8.0.11",
|
"@types/koa__router": "8.0.11",
|
||||||
|
"@types/mocha": "9.1.1",
|
||||||
"@types/node": "18.7.16",
|
"@types/node": "18.7.16",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.5",
|
"@types/nodemailer": "6.4.5",
|
||||||
|
@ -142,6 +164,7 @@
|
||||||
"@types/ws": "8.5.3",
|
"@types/ws": "8.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||||
"@typescript-eslint/parser": "^5.46.1",
|
"@typescript-eslint/parser": "^5.46.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
|
14
packages/backend/src/@types/koa-slow.d.ts
vendored
Normal file
14
packages/backend/src/@types/koa-slow.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
declare module 'koa-slow' {
|
||||||
|
import { Middleware } from 'koa';
|
||||||
|
|
||||||
|
interface ISlowOptions {
|
||||||
|
url?: RegExp;
|
||||||
|
delay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function slow(options?: ISlowOptions): Middleware;
|
||||||
|
|
||||||
|
namespace slow {} // Hack
|
||||||
|
|
||||||
|
export = slow;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import cluster from 'node:cluster';
|
import cluster from 'node:cluster';
|
||||||
import net from 'node:net';
|
import chalk from 'chalk';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
|
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
|
@ -10,8 +10,8 @@ import 'reflect-metadata';
|
||||||
import { masterMain } from './master.js';
|
import { masterMain } from './master.js';
|
||||||
import { workerMain } from './worker.js';
|
import { workerMain } from './worker.js';
|
||||||
|
|
||||||
const logger = new Logger('core');
|
const logger = new Logger('core', 'cyan');
|
||||||
const clusterLogger = logger.createSubLogger('cluster');
|
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,8 +26,6 @@ export async function boot(): Promise<void> {
|
||||||
process.title = `Foundkey (${process.env.mode})`;
|
process.title = `Foundkey (${process.env.mode})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
net.setDefaultAutoSelectFamily(true);
|
|
||||||
|
|
||||||
if (cluster.isPrimary || envOption.disableClustering) {
|
if (cluster.isPrimary || envOption.disableClustering) {
|
||||||
await masterMain();
|
await masterMain();
|
||||||
|
|
||||||
|
@ -59,6 +57,14 @@ cluster.on('online', worker => {
|
||||||
clusterLogger.debug(`Process is now online: [${worker.id}]`);
|
clusterLogger.debug(`Process is now online: [${worker.id}]`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for dying workers
|
||||||
|
cluster.on('exit', worker => {
|
||||||
|
// Replace the dead worker,
|
||||||
|
// we're not sentimental
|
||||||
|
clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
|
||||||
|
cluster.fork();
|
||||||
|
});
|
||||||
|
|
||||||
// Display detail of unhandled promise rejection
|
// Display detail of unhandled promise rejection
|
||||||
if (envOption.logLevel !== LOG_LEVELS.quiet) {
|
if (envOption.logLevel !== LOG_LEVELS.quiet) {
|
||||||
process.on('unhandledRejection', console.dir);
|
process.on('unhandledRejection', console.dir);
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import cluster from 'node:cluster';
|
import cluster from 'node:cluster';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import chalkTemplate from 'chalk-template';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
|
@ -17,27 +19,29 @@ const _dirname = dirname(_filename);
|
||||||
|
|
||||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
||||||
|
|
||||||
const logger = new Logger('core');
|
const logger = new Logger('core', 'cyan');
|
||||||
const bootLogger = logger.createSubLogger('boot');
|
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
||||||
|
|
||||||
|
const themeColor = chalk.hex('#86b300');
|
||||||
|
|
||||||
function greet(): void {
|
function greet(): void {
|
||||||
if (envOption.logLevel !== LOG_LEVELS.quiet) {
|
if (envOption.logLevel !== LOG_LEVELS.quiet) {
|
||||||
//#region FoundKey logo
|
//#region FoundKey logo
|
||||||
console.log(' ___ _ _ __ ');
|
console.log(themeColor(' ___ _ _ __ '));
|
||||||
console.log(' | __|__ _ _ _ _ __| | |/ /___ _ _ ');
|
console.log(themeColor(' | __|__ _ _ _ _ __| | |/ /___ _ _ '));
|
||||||
console.log(' | _/ _ \\ || | \' \\/ _` | \' </ -_) || |');
|
console.log(themeColor(' | _/ _ \\ || | \' \\/ _` | \' </ -_) || |'));
|
||||||
console.log(' |_|\\___/\\_,_|_||_\\__,_|_|\\_\\___|\\_, |');
|
console.log(themeColor(' |_|\\___/\\_,_|_||_\\__,_|_|\\_\\___|\\_, |'));
|
||||||
console.log(' |__/ ');
|
console.log(themeColor(' |__/ '));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
console.log(' FoundKey is an open-source decentralized microblogging platform.');
|
console.log(' FoundKey is an open-source decentralized microblogging platform.');
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(`--- ${os.hostname()} (PID: ${process.pid.toString()}) ---`);
|
console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootLogger.info('Welcome to FoundKey!');
|
bootLogger.info('Welcome to FoundKey!');
|
||||||
bootLogger.info(`FoundKey v${meta.version}`);
|
bootLogger.info(`FoundKey v${meta.version}`, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,7 +59,7 @@ export async function masterMain(): Promise<void> {
|
||||||
config = loadConfigBoot();
|
config = loadConfigBoot();
|
||||||
await connectDb();
|
await connectDb();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bootLogger.error('Fatal error occurred during initialization');
|
bootLogger.error('Fatal error occurred during initialization', true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +69,7 @@ export async function masterMain(): Promise<void> {
|
||||||
await spawnWorkers(config.clusterLimits);
|
await spawnWorkers(config.clusterLimits);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`);
|
bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, true);
|
||||||
|
|
||||||
if (!envOption.noDaemons) {
|
if (!envOption.noDaemons) {
|
||||||
import('../daemons/server-stats.js').then(x => x.serverStats());
|
import('../daemons/server-stats.js').then(x => x.serverStats());
|
||||||
|
@ -80,7 +84,7 @@ function showEnvironment(): void {
|
||||||
|
|
||||||
if (env !== 'production') {
|
if (env !== 'production') {
|
||||||
logger.warn('The environment is not in production mode.');
|
logger.warn('The environment is not in production mode.');
|
||||||
logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!');
|
logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +109,7 @@ function loadConfigBoot(): Config {
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const e = exception as Partial<NodeJS.ErrnoException> | Error;
|
const e = exception as Partial<NodeJS.ErrnoException> | Error;
|
||||||
if ('code' in e && e.code === 'ENOENT') {
|
if ('code' in e && e.code === 'ENOENT') {
|
||||||
configLogger.error('Configuration file not found');
|
configLogger.error('Configuration file not found', true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else if (e instanceof Error) {
|
} else if (e instanceof Error) {
|
||||||
configLogger.error(e.message);
|
configLogger.error(e.message);
|
||||||
|
@ -129,7 +133,7 @@ async function connectDb(): Promise<void> {
|
||||||
const v = await db.query('SHOW server_version').then(x => x[0].server_version);
|
const v = await db.query('SHOW server_version').then(x => x[0].server_version);
|
||||||
dbLogger.succ(`Connected: v${v}`);
|
dbLogger.succ(`Connected: v${v}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dbLogger.error('Cannot connect');
|
dbLogger.error('Cannot connect', true);
|
||||||
dbLogger.error(e as Error | string);
|
dbLogger.error(e as Error | string);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
@ -164,10 +168,6 @@ async function spawnWorkers(clusterLimits: Required<Config['clusterLimits']>): P
|
||||||
function spawnWorker(mode: 'web' | 'queue'): Promise<void> {
|
function spawnWorker(mode: 'web' | 'queue'): Promise<void> {
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
const worker = cluster.fork({ mode });
|
const worker = cluster.fork({ mode });
|
||||||
worker.on('exit', async (code, signal) => {
|
|
||||||
logger.error(mode + ' worker died, restarting...');
|
|
||||||
await spawnWorker(mode);
|
|
||||||
});
|
|
||||||
worker.on('message', message => {
|
worker.on('message', message => {
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case 'listenFailed':
|
case 'listenFailed':
|
||||||
|
|
|
@ -61,7 +61,6 @@ export function loadConfig(): Config {
|
||||||
proxyRemoteFiles: false,
|
proxyRemoteFiles: false,
|
||||||
maxFileSize: 262144000, // 250 MiB
|
maxFileSize: 262144000, // 250 MiB
|
||||||
maxNoteTextLength: 3000,
|
maxNoteTextLength: 3000,
|
||||||
allowUnsignedFetches: false,
|
|
||||||
}, config);
|
}, config);
|
||||||
|
|
||||||
mixin.version = meta.version;
|
mixin.version = meta.version;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
import config from './index.js';
|
import config from './index.js';
|
||||||
|
|
||||||
const logger = new Logger('config:redis');
|
const logger = new Logger('config:redis', 'gray', false);
|
||||||
|
|
||||||
function getRedisFamily(family?: string | number): number {
|
function getRedisFamily(family?: string | number): number {
|
||||||
const familyMap = {
|
const familyMap = {
|
||||||
|
|
|
@ -24,6 +24,14 @@ export type Source = {
|
||||||
db?: number;
|
db?: number;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
};
|
};
|
||||||
|
elasticsearch?: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
ssl?: boolean;
|
||||||
|
user?: string;
|
||||||
|
pass?: string;
|
||||||
|
index?: string;
|
||||||
|
};
|
||||||
|
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
proxySmtp?: string;
|
proxySmtp?: string;
|
||||||
|
@ -60,8 +68,6 @@ export type Source = {
|
||||||
notFound?: string;
|
notFound?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
allowUnsignedFetches?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
56
packages/backend/src/db/elasticsearch.ts
Normal file
56
packages/backend/src/db/elasticsearch.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import * as elasticsearch from '@elastic/elasticsearch';
|
||||||
|
import config from '@/config/index.js';
|
||||||
|
|
||||||
|
const index = {
|
||||||
|
settings: {
|
||||||
|
analysis: {
|
||||||
|
analyzer: {
|
||||||
|
ngram: {
|
||||||
|
tokenizer: 'ngram',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mappings: {
|
||||||
|
properties: {
|
||||||
|
text: {
|
||||||
|
type: 'text',
|
||||||
|
index: true,
|
||||||
|
analyzer: 'ngram',
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: 'keyword',
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
userHost: {
|
||||||
|
type: 'keyword',
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Init ElasticSearch connection
|
||||||
|
const client = config.elasticsearch ? new elasticsearch.Client({
|
||||||
|
node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`,
|
||||||
|
auth: (config.elasticsearch.user && config.elasticsearch.pass) ? {
|
||||||
|
username: config.elasticsearch.user,
|
||||||
|
password: config.elasticsearch.pass,
|
||||||
|
} : undefined,
|
||||||
|
pingTimeout: 30000,
|
||||||
|
}) : null;
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
client.indices.exists({
|
||||||
|
index: config.elasticsearch.index || 'misskey_note',
|
||||||
|
}).then(exist => {
|
||||||
|
if (!exist.body) {
|
||||||
|
client.indices.create({
|
||||||
|
index: config.elasticsearch.index || 'misskey_note',
|
||||||
|
body: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default client;
|
|
@ -4,6 +4,7 @@ import pg from 'pg';
|
||||||
pg.types.setTypeParser(20, Number);
|
pg.types.setTypeParser(20, Number);
|
||||||
|
|
||||||
import { Logger, DataSource } from 'typeorm';
|
import { Logger, DataSource } from 'typeorm';
|
||||||
|
import * as highlight from 'cli-highlight';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
|
|
||||||
import { SECOND } from '@/const.js';
|
import { SECOND } from '@/const.js';
|
||||||
|
@ -69,20 +70,27 @@ import { entities as charts } from '@/services/chart/entities.js';
|
||||||
import { Webhook } from '@/models/entities/webhook.js';
|
import { Webhook } from '@/models/entities/webhook.js';
|
||||||
import { getRedisOptions } from '@/config/redis.js';
|
import { getRedisOptions } from '@/config/redis.js';
|
||||||
import { dbLogger } from './logger.js';
|
import { dbLogger } from './logger.js';
|
||||||
|
import { redisClient } from './redis.js';
|
||||||
|
|
||||||
const sqlLogger = dbLogger.createSubLogger('sql');
|
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||||
|
|
||||||
class MyCustomLogger implements Logger {
|
class MyCustomLogger implements Logger {
|
||||||
|
private highlight(sql: string): string {
|
||||||
|
return highlight.highlight(sql, {
|
||||||
|
language: 'sql', ignoreIllegals: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public logQuery(query: string): void {
|
public logQuery(query: string): void {
|
||||||
sqlLogger.info(query);
|
sqlLogger.info(this.highlight(query).substring(0, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
public logQueryError(error: string, query: string): void {
|
public logQueryError(error: string, query: string): void {
|
||||||
sqlLogger.error(query);
|
sqlLogger.error(this.highlight(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
public logQuerySlow(time: number, query: string): void {
|
public logQuerySlow(time: number, query: string): void {
|
||||||
sqlLogger.warn(query);
|
sqlLogger.warn(this.highlight(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
public logSchemaBuild(message: string): void {
|
public logSchemaBuild(message: string): void {
|
||||||
|
@ -201,3 +209,31 @@ export async function initDb(force = false) {
|
||||||
await db.initialize();
|
await db.initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resetDb() {
|
||||||
|
const reset = async () => {
|
||||||
|
await redisClient.flushdb();
|
||||||
|
const tables = await db.query(`SELECT relname AS "table"
|
||||||
|
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||||
|
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
||||||
|
AND C.relkind = 'r'
|
||||||
|
AND nspname !~ '^pg_toast';`);
|
||||||
|
for (const table of tables) {
|
||||||
|
await db.query(`DELETE FROM "${table.table}" CASCADE`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
try {
|
||||||
|
await reset();
|
||||||
|
} catch (e) {
|
||||||
|
if (i === 3) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, SECOND));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const envOption = {
|
||||||
noDaemons: false,
|
noDaemons: false,
|
||||||
disableClustering: false,
|
disableClustering: false,
|
||||||
withLogTime: false,
|
withLogTime: false,
|
||||||
|
slow: false,
|
||||||
logLevel: LOG_LEVELS.info,
|
logLevel: LOG_LEVELS.info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,9 +24,7 @@ for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {
|
||||||
if (value.toLowerCase() in LOG_LEVELS) {
|
if (value.toLowerCase() in LOG_LEVELS) {
|
||||||
envOption.logLevel = LOG_LEVELS[value.toLowerCase()];
|
envOption.logLevel = LOG_LEVELS[value.toLowerCase()];
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
console.log('Unknown log level ' + JSON.stringify(value.toLowerCase()) + ', defaulting to "info"');
|
console.log('Unknown log level ' + JSON.stringify(value.toLowerCase()) + ', defaulting to "info"');
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
envOption[key] = true;
|
envOption[key] = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,13 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
||||||
html.replace(/<br\s?\/?>\r?\n/gi, '\n'),
|
html.replace(/<br\s?\/?>\r?\n/gi, '\n'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// stores if we are parsing any lists.
|
let text = '';
|
||||||
// 0 for a level that is an unordered list, otherwise the counter for the ordered list
|
|
||||||
let listIndex: number[] = [];
|
|
||||||
|
|
||||||
return dom.childNodes
|
for (const n of dom.childNodes) {
|
||||||
.map(analyze)
|
analyze(n);
|
||||||
.join('')
|
}
|
||||||
.trim();
|
|
||||||
|
return text.trim();
|
||||||
|
|
||||||
function getText(node: TreeAdapter.Node): string {
|
function getText(node: TreeAdapter.Node): string {
|
||||||
if (treeAdapter.isTextNode(node)) return node.value;
|
if (treeAdapter.isTextNode(node)) return node.value;
|
||||||
|
@ -44,55 +43,58 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function analyzeMultiple(childNodes: TreeAdapter.ChildNode[]): string {
|
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
|
||||||
return childNodes.map(analyze).join('');
|
if (childNodes.length > 0) {
|
||||||
|
for (const n of childNodes) {
|
||||||
|
analyze(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function analyze(node: TreeAdapter.Node): string {
|
function analyze(node: TreeAdapter.Node): void {
|
||||||
if (treeAdapter.isTextNode(node)) {
|
if (treeAdapter.isTextNode(node)) {
|
||||||
return node.value;
|
text += node.value;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip comment or document type node
|
// Skip comment or document type node
|
||||||
if (!treeAdapter.isElementNode(node)) return '';
|
if (!treeAdapter.isElementNode(node)) return;
|
||||||
|
|
||||||
switch (node.nodeName) {
|
switch (node.nodeName) {
|
||||||
case 'br': {
|
case 'br': {
|
||||||
return '\n';
|
text += '\n';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
{
|
{
|
||||||
let text = '';
|
const txt = getText(node);
|
||||||
// trim spaces away, because some AP servers (app.wafrn.net) send strange
|
|
||||||
// zero width non-break space in strange places and things like that
|
|
||||||
const linkText = getText(node).trim();
|
|
||||||
const href = getAttr(node, 'href');
|
const href = getAttr(node, 'href');
|
||||||
|
|
||||||
// hashtags
|
// hashtags
|
||||||
if (linkText.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
|
if (txt.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
|
||||||
text += linkText;
|
text += txt;
|
||||||
// mentions: a link that starts with `@` and does not include space
|
// mentions
|
||||||
} else if (linkText.startsWith('@') && linkText.match(/\s/) == null && !attrHas(node, 'rel', 'me')) {
|
} else if (txt.startsWith('@') && !attrHas(node, 'rel', 'me')) {
|
||||||
const part = linkText.split('@');
|
const part = txt.split('@');
|
||||||
|
|
||||||
if (part.length === 2 && href) {
|
if (part.length === 2 && href) {
|
||||||
// restore the host name part
|
// restore the host name part
|
||||||
const acct = `${linkText}@${(new URL(href)).hostname}`;
|
const acct = `${txt}@${(new URL(href)).hostname}`;
|
||||||
text += acct;
|
text += acct;
|
||||||
} else if (part.length === 3) {
|
} else if (part.length === 3) {
|
||||||
text += linkText;
|
text += txt;
|
||||||
}
|
}
|
||||||
// other
|
// other
|
||||||
} else {
|
} else {
|
||||||
const generateLink = () => {
|
const generateLink = () => {
|
||||||
if (!href && !linkText) {
|
if (!href && !txt) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (!href) {
|
if (!href) {
|
||||||
return linkText;
|
return txt;
|
||||||
}
|
}
|
||||||
if (!linkText || linkText === href) { // #6383: Missing text node
|
if (!txt || txt === href) { // #6383: Missing text node
|
||||||
if (href.match(urlRegexFull)) {
|
if (href.match(urlRegexFull)) {
|
||||||
return href;
|
return href;
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,109 +102,87 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (href.match(urlRegex) && !href.match(urlRegexFull)) {
|
if (href.match(urlRegex) && !href.match(urlRegexFull)) {
|
||||||
return `[${linkText}](<${href}>)`; // #6846
|
return `[${txt}](<${href}>)`; // #6846
|
||||||
} else {
|
} else {
|
||||||
return `[${linkText}](${href})`;
|
return `[${txt}](${href})`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
text += generateLink();
|
text += generateLink();
|
||||||
}
|
}
|
||||||
return text;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'h1':
|
case 'h1':
|
||||||
{
|
{
|
||||||
return '【' + analyzeMultiple(node.childNodes) + '】\n';
|
text += '【';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
text += '】\n';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'b':
|
case 'b':
|
||||||
case 'strong':
|
case 'strong':
|
||||||
{
|
{
|
||||||
return '**' + analyzeMultiple(node.childNodes) + '**';
|
text += '**';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
text += '**';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'small':
|
case 'small':
|
||||||
{
|
{
|
||||||
return '<small>' + analyzeMultiple(node.childNodes) + '</small>';
|
text += '<small>';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
text += '</small>';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
case 'del':
|
case 'del':
|
||||||
{
|
{
|
||||||
return '~~' + analyzeMultiple(node.childNodes) + '~~';
|
text += '~~';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
text += '~~';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
case 'em':
|
case 'em':
|
||||||
{
|
{
|
||||||
return '<i>' + analyzeMultiple(node.childNodes) + '</i>';
|
text += '<i>';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
text += '</i>';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// block code (<pre><code>)
|
// block code (<pre><code>)
|
||||||
case 'pre': {
|
case 'pre': {
|
||||||
if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
|
if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
|
||||||
return '\n```\n' + getText(node.childNodes[0]) + '\n```\n';
|
text += '\n```\n';
|
||||||
|
text += getText(node.childNodes[0]);
|
||||||
|
text += '\n```\n';
|
||||||
} else {
|
} else {
|
||||||
return analyzeMultiple(node.childNodes);
|
appendChildren(node.childNodes);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inline code (<code>)
|
// inline code (<code>)
|
||||||
case 'code': {
|
case 'code': {
|
||||||
return '`' + analyzeMultiple(node.childNodes) + '`';
|
text += '`';
|
||||||
}
|
appendChildren(node.childNodes);
|
||||||
|
text += '`';
|
||||||
// inline or block KaTeX
|
|
||||||
case 'math': {
|
|
||||||
// This node should contain <semantics>[...]<annotation/>[...]</semantics> tag with the "source code".
|
|
||||||
if (node.childNodes.length !== 1 || node.childNodes[0].nodeName !== 'semantics')
|
|
||||||
break;
|
break;
|
||||||
const semantics = node.childNodes[0];
|
|
||||||
|
|
||||||
// only select well formed annotations
|
|
||||||
const annotations = semantics.childNodes
|
|
||||||
.filter(node =>
|
|
||||||
node.nodeName === 'annotation'
|
|
||||||
&& node.childNodes.length === 1
|
|
||||||
&& node.childNodes[0].nodeName === '#text'
|
|
||||||
);
|
|
||||||
if (annotations.length === 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
let annotation = annotations[0];
|
|
||||||
// try to prefer a TeX annotation if there are multiple annotations
|
|
||||||
const filteredAnnotations = annotations.filter(node => node.attrs.some(attribute => attribute.name === 'encoding' && attribute.value === 'application/x-tex'));
|
|
||||||
if (filteredAnnotations.length > 0) {
|
|
||||||
annotation = filteredAnnotations[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const formula = annotation.childNodes[0].value;
|
|
||||||
if (annotation.attrs.some(attribute => attribute.name === 'encoding' && attribute.value === 'application/x-tex')) {
|
|
||||||
// can be rendered as KaTeX, now decide if it is possible to render as inline or not
|
|
||||||
if (/[\r\n]/.test(formula)) {
|
|
||||||
// line break, this must be rendered as a block
|
|
||||||
return '\n\\[' + formula + '\\]\n';
|
|
||||||
} else {
|
|
||||||
// render as inline
|
|
||||||
return '\\(' + formula + '\\)';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// not KaTeX, but if there is a plaintext annotation it can still be rendered as code
|
|
||||||
if (/[\r\n]/.test(formula)) {
|
|
||||||
// line break, this must be rendered as a block
|
|
||||||
return '\n```\n' + formula + '\n```\n';
|
|
||||||
} else {
|
|
||||||
// render as inline
|
|
||||||
return '`' + formula + '`';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blockquote': {
|
case 'blockquote': {
|
||||||
return analyzeMultiple(node.childNodes)
|
const t = getText(node);
|
||||||
.trim()
|
if (t) {
|
||||||
.replace(/^|\n/g, '\n>');
|
text += '\n> ';
|
||||||
|
text += t.split('\n').join('\n> ');
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
|
@ -212,79 +192,40 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
||||||
case 'h5':
|
case 'h5':
|
||||||
case 'h6':
|
case 'h6':
|
||||||
{
|
{
|
||||||
return '\n\n' + analyzeMultiple(node.childNodes);
|
text += '\n\n';
|
||||||
}
|
appendChildren(node.childNodes);
|
||||||
|
|
||||||
// lists and list items
|
|
||||||
case 'ol':
|
|
||||||
case 'ul':
|
|
||||||
{
|
|
||||||
if (node.nodeName == 'ol') {
|
|
||||||
listIndex.push(1);
|
|
||||||
} else {
|
|
||||||
listIndex.push(0);
|
|
||||||
}
|
|
||||||
let text = '\n' + analyzeMultiple(node.childNodes);
|
|
||||||
listIndex.pop();
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'li':
|
|
||||||
{
|
|
||||||
if (listIndex.length == 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let text = '\n';
|
|
||||||
|
|
||||||
// pop the current operating on index for manipulation
|
|
||||||
let index = listIndex.pop();
|
|
||||||
// indent the start of the list item respecitve of the level of
|
|
||||||
// nesting of lists
|
|
||||||
//
|
|
||||||
// since we popped the current index, the length will be 0 on
|
|
||||||
// the first level, thus causing no indent on the first level
|
|
||||||
text += ' '.repeat(listIndex.length);
|
|
||||||
if (index == 0) {
|
|
||||||
text += '- ';
|
|
||||||
} else {
|
|
||||||
text += index + ') ';
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
// done with the index, put it back so nested lists with
|
|
||||||
// analyzeMultiple will work correctly
|
|
||||||
listIndex.push(index);
|
|
||||||
|
|
||||||
text += analyzeMultiple(node.childNodes);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// other block elements
|
// other block elements
|
||||||
case 'div':
|
case 'div':
|
||||||
case 'header':
|
case 'header':
|
||||||
case 'footer':
|
case 'footer':
|
||||||
case 'article':
|
case 'article':
|
||||||
|
case 'li':
|
||||||
case 'dt':
|
case 'dt':
|
||||||
case 'dd':
|
case 'dd':
|
||||||
{
|
{
|
||||||
return '\n' + analyzeMultiple(node.childNodes);
|
text += '\n';
|
||||||
|
appendChildren(node.childNodes);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'span':
|
case 'span':
|
||||||
{
|
{
|
||||||
if (attrHas(node, 'class', 'quote-inline') && quoteUri && getText(node).trim() === `RE: ${quoteUri}`) {
|
if (attrHas(node, 'class', 'quote-inline') && quoteUri && getText(node).trim() === `RE: ${quoteUri}`) {
|
||||||
// embedded quote thingy for backwards compatibility, don't show it
|
// embedded quote thingy for backwards compatibility, don't show it
|
||||||
return '';
|
|
||||||
} else {
|
} else {
|
||||||
return analyzeMultiple(node.childNodes);
|
appendChildren(node.childNodes);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: // includes inline elements
|
default: // includes inline elements
|
||||||
{
|
{
|
||||||
return analyzeMultiple(node.childNodes);
|
appendChildren(node.childNodes);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import katex from 'katex';
|
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import { UserProfiles } from '@/models/index.js';
|
import { UserProfiles } from '@/models/index.js';
|
||||||
|
@ -7,14 +6,6 @@ import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { intersperse } from '@/prelude/array.js';
|
import { intersperse } from '@/prelude/array.js';
|
||||||
import { toPunyNullable } from '@/misc/convert-host.js';
|
import { toPunyNullable } from '@/misc/convert-host.js';
|
||||||
|
|
||||||
function toMathMl(code: string): HTMLElement {
|
|
||||||
const rendered = katex.renderToString(code, {
|
|
||||||
throwOnError: false,
|
|
||||||
output: 'mathml',
|
|
||||||
});
|
|
||||||
return JSDOM.fragment(rendered).querySelector('math');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transforms MFM to HTML, given the MFM text and a list of user IDs that are
|
// Transforms MFM to HTML, given the MFM text and a list of user IDs that are
|
||||||
// mentioned in the text. If the list of mentions is not given, all mentions
|
// mentioned in the text. If the list of mentions is not given, all mentions
|
||||||
// from the text will be extracted.
|
// from the text will be extracted.
|
||||||
|
@ -107,11 +98,15 @@ export async function toHtml(mfmText: string, mentions?: string[]): Promise<stri
|
||||||
},
|
},
|
||||||
|
|
||||||
async mathInline(node) {
|
async mathInline(node) {
|
||||||
return toMathMl(node.props.formula);
|
const el = doc.createElement('code');
|
||||||
|
el.textContent = node.props.formula;
|
||||||
|
return el;
|
||||||
},
|
},
|
||||||
|
|
||||||
async mathBlock(node) {
|
async mathBlock(node) {
|
||||||
return toMathMl(node.props.formula);
|
const el = doc.createElement('code');
|
||||||
|
el.textContent = node.props.formula;
|
||||||
|
return el;
|
||||||
},
|
},
|
||||||
|
|
||||||
async link(node) {
|
async link(node) {
|
||||||
|
|
29
packages/backend/src/misc/api-permissions.ts
Normal file
29
packages/backend/src/misc/api-permissions.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
export const kinds = [
|
||||||
|
'read:account',
|
||||||
|
'write:account',
|
||||||
|
'read:blocks',
|
||||||
|
'write:blocks',
|
||||||
|
'read:drive',
|
||||||
|
'write:drive',
|
||||||
|
'read:following',
|
||||||
|
'write:following',
|
||||||
|
'read:messaging',
|
||||||
|
'write:messaging',
|
||||||
|
'read:mutes',
|
||||||
|
'write:mutes',
|
||||||
|
'write:notes',
|
||||||
|
'read:notifications',
|
||||||
|
'write:notifications',
|
||||||
|
'read:reactions',
|
||||||
|
'write:reactions',
|
||||||
|
'write:votes',
|
||||||
|
'read:pages',
|
||||||
|
'write:pages',
|
||||||
|
'write:page-likes',
|
||||||
|
'read:page-likes',
|
||||||
|
'read:user-groups',
|
||||||
|
'write:user-groups',
|
||||||
|
'read:channels',
|
||||||
|
'write:channels',
|
||||||
|
];
|
||||||
|
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).
|
|
@ -1,23 +1,12 @@
|
||||||
export class Cache<T> {
|
export class Cache<T> {
|
||||||
// The actual "database" that holds the cache entries, along with their
|
|
||||||
// insertion time.
|
|
||||||
// Insertion order is the same as the order of elements expiring. This is
|
|
||||||
// important because the expiration logic relies on the insertion order.
|
|
||||||
public cache: Map<string, { date: number; value: T; }>;
|
public cache: Map<string, { date: number; value: T; }>;
|
||||||
// The lifetime of each cache member.
|
|
||||||
//
|
|
||||||
// This must not be changed after setup because it may upset
|
|
||||||
// the expiration logic.
|
|
||||||
private lifetime: number;
|
private lifetime: number;
|
||||||
// Function of which the results should be cached.
|
|
||||||
public fetcher: (key: string) => Promise<T | undefined>;
|
public fetcher: (key: string) => Promise<T | undefined>;
|
||||||
private timeoutScheduled: boolean;
|
|
||||||
|
|
||||||
constructor(lifetime: number, fetcher: Cache<T>['fetcher']) {
|
constructor(lifetime: number, fetcher: Cache<T>['fetcher']) {
|
||||||
this.cache = new Map();
|
this.cache = new Map();
|
||||||
this.lifetime = lifetime;
|
this.lifetime = lifetime;
|
||||||
this.fetcher = fetcher;
|
this.fetcher = fetcher;
|
||||||
this.timeoutScheduled = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(key: string, value: T): void {
|
public set(key: string, value: T): void {
|
||||||
|
@ -25,36 +14,38 @@ export class Cache<T> {
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
// make sure the expiration timeout is in place
|
|
||||||
this.expire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(key: string): T | undefined {
|
public get(key: string): T | undefined {
|
||||||
const cached = this.cache.get(key);
|
const cached = this.cache.get(key);
|
||||||
if (cached == null) return undefined;
|
if (cached == null) return undefined;
|
||||||
else return cached.value;
|
|
||||||
|
// discard if past the cache lifetime
|
||||||
|
if ((Date.now() - cached.date) > this.lifetime) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cached.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(key: string): void {
|
public delete(key: string): void {
|
||||||
this.cache.delete(key);
|
this.cache.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the value is cached, it is returned. Otherwise the fetcher is
|
/**
|
||||||
// run to get the value. If the fetcher returns undefined, it is
|
* If the value is cached, it is returned. Otherwise the fetcher is
|
||||||
// returned but not cached.
|
* run to get the value. If the fetcher returns undefined, it is
|
||||||
|
* returned but not cached.
|
||||||
|
*/
|
||||||
public async fetch(key: string): Promise<T | undefined> {
|
public async fetch(key: string): Promise<T | undefined> {
|
||||||
// Check if this value is cached
|
|
||||||
const cached = this.get(key);
|
const cached = this.get(key);
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
// The value was cached, return it.
|
|
||||||
return cached;
|
return cached;
|
||||||
} else {
|
} else {
|
||||||
// The value was not cached, need to call the original function
|
|
||||||
// to get its result and then cache it.
|
|
||||||
const value = await this.fetcher(key);
|
const value = await this.fetcher(key);
|
||||||
|
|
||||||
// `undefined` is not cached
|
// don't cache undefined
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
this.set(key, value);
|
this.set(key, value);
|
||||||
}
|
}
|
||||||
|
@ -62,43 +53,4 @@ export class Cache<T> {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handling the expiration of cached values.
|
|
||||||
// This is done using a timeout.
|
|
||||||
private expire(): void {
|
|
||||||
// If there already is a timeout scheduled, it will be appropriate
|
|
||||||
// for the first inserted element of the cache.
|
|
||||||
// If the first element of the cache was removed, it will reschedule
|
|
||||||
// to the appropriate time when it runs out.
|
|
||||||
//
|
|
||||||
// If the cache is empty, there is nothing to expire either.
|
|
||||||
if (this.timeoutScheduled) return;
|
|
||||||
// Otherwise, this must mean this is the previously scheduled timeout.
|
|
||||||
// Since it is running now, it is no longer scheduled.
|
|
||||||
this.timeoutScheduled = false;
|
|
||||||
|
|
||||||
// Check if the first element is actually due for expiration.
|
|
||||||
//
|
|
||||||
// Items may have been removed in the meantime or this may be
|
|
||||||
// the initial call for the first key inserted into the cache.
|
|
||||||
const [expiredKey, expiredValue] = this.cache.entries().next().value;
|
|
||||||
if (expiredValue.date + this.lifetime <= Date.now()) {
|
|
||||||
// This item is due for expiration, so remove it.
|
|
||||||
this.cache.delete(expiredKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no further elements in the cache, there is nothing to
|
|
||||||
// expire at a later time. The timeout will be set up again later by
|
|
||||||
// a call from `this.set`.
|
|
||||||
if (this.cache.size === 0) return;
|
|
||||||
|
|
||||||
// Check when the next key is due for removal and schedule
|
|
||||||
// an appropriate timeout.
|
|
||||||
const [nextKey, nextValue] = this.cache.entries().next().value;
|
|
||||||
setTimeout(
|
|
||||||
() => this.expire(),
|
|
||||||
nextValue.date + this.lifetime - Date.now()
|
|
||||||
);
|
|
||||||
this.timeoutScheduled = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { URLSearchParams } from 'node:url';
|
import { URLSearchParams } from 'node:url';
|
||||||
import { getResponse } from '@/misc/fetch.js';
|
import fetch from 'node-fetch';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
|
import { getAgentByUrl } from './fetch.js';
|
||||||
|
|
||||||
export async function verifyRecaptcha(secret: string, response: string): Promise<void> {
|
export async function verifyRecaptcha(secret: string, response: string): Promise<void> {
|
||||||
const result = await getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
|
const result = await getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
|
||||||
|
@ -35,10 +36,15 @@ async function getCaptchaResponse(url: string, secret: string, response: string)
|
||||||
response,
|
response,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await getResponse({
|
const res = await fetch(url, {
|
||||||
url,
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: params,
|
body: params,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': config.userAgent,
|
||||||
|
},
|
||||||
|
// TODO
|
||||||
|
//timeout: 10 * 1000,
|
||||||
|
agent: getAgentByUrl,
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
throw new Error(`${e.message || e}`);
|
throw new Error(`${e.message || e}`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,31 +13,17 @@ type UserLike = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array<string | string[]>): Promise<boolean> {
|
export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array<string | string[]>): Promise<boolean> {
|
||||||
// own posts
|
// 自分自身
|
||||||
if (me && (note.userId === me.id)) return false;
|
if (me && (note.userId === me.id)) return false;
|
||||||
|
|
||||||
if (mutedWords.length > 0) {
|
if (mutedWords.length > 0) {
|
||||||
const text = [
|
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
||||||
note.cw,
|
|
||||||
note.text,
|
|
||||||
...note.files.map(file => file.comment)
|
|
||||||
]
|
|
||||||
.map(x => {
|
|
||||||
if (x == null || x.trim() == '') {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return x.trim();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(x => x != null)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
const textLower = text.toLowerCase();
|
|
||||||
|
|
||||||
const matched = mutedWords.some(filter => {
|
const matched = mutedWords.some(filter => {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
return filter.every(keyword => textLower.includes(keyword.toLowerCase()));
|
return filter.every(keyword => text.includes(keyword));
|
||||||
} else {
|
} else {
|
||||||
// represents RegExp
|
// represents RegExp
|
||||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function isSelfHost(host: string | null): boolean {
|
||||||
return toPuny(config.host) === toPuny(host);
|
return toPuny(config.host) === toPuny(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractPunyHost(uri: string): string {
|
export function extractDbHost(uri: string): string {
|
||||||
const url = new URL(uri);
|
const url = new URL(uri);
|
||||||
return toPuny(url.hostname);
|
return toPuny(url.hostname);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as stream from 'node:stream';
|
import * as stream from 'node:stream';
|
||||||
import * as util from 'node:util';
|
import * as util from 'node:util';
|
||||||
|
import chalk from 'chalk';
|
||||||
import got, * as Got from 'got';
|
import got, * as Got from 'got';
|
||||||
|
import IPCIDR from 'ip-cidr';
|
||||||
|
import PrivateIp from 'private-ip';
|
||||||
import { SECOND, MINUTE } from '@/const.js';
|
import { SECOND, MINUTE } from '@/const.js';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
|
@ -12,7 +15,7 @@ const pipeline = util.promisify(stream.pipeline);
|
||||||
export async function downloadUrl(url: string, path: string): Promise<void> {
|
export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
const logger = new Logger('download');
|
const logger = new Logger('download');
|
||||||
|
|
||||||
logger.info(`Downloading ${url} ...`);
|
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||||
|
|
||||||
const timeout = 30 * SECOND;
|
const timeout = 30 * SECOND;
|
||||||
const operationTimeout = MINUTE;
|
const operationTimeout = MINUTE;
|
||||||
|
@ -39,6 +42,13 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
limit: 0,
|
limit: 0,
|
||||||
},
|
},
|
||||||
}).on('response', (res: Got.Response) => {
|
}).on('response', (res: Got.Response) => {
|
||||||
|
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !config.proxy && res.ip) {
|
||||||
|
if (isPrivateIp(res.ip)) {
|
||||||
|
logger.warn(`Blocked address: ${res.ip}`);
|
||||||
|
req.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const contentLength = res.headers['content-length'];
|
const contentLength = res.headers['content-length'];
|
||||||
if (contentLength != null) {
|
if (contentLength != null) {
|
||||||
const size = Number(contentLength);
|
const size = Number(contentLength);
|
||||||
|
@ -64,5 +74,16 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.succ(`Download finished: ${url}`);
|
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateIp(ip: string): boolean {
|
||||||
|
for (const net of config.allowedPrivateNetworks || []) {
|
||||||
|
const cidr = new IPCIDR(net);
|
||||||
|
if (cidr.contains(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrivateIp(ip);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,57 @@
|
||||||
import * as http from 'node:http';
|
import * as http from 'node:http';
|
||||||
import * as https from 'node:https';
|
import * as https from 'node:https';
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import * as dns from 'node:dns';
|
|
||||||
import CacheableLookup from 'cacheable-lookup';
|
import CacheableLookup from 'cacheable-lookup';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||||
import ipaddr from 'ipaddr.js';
|
|
||||||
import IPCIDR from 'ip-cidr';
|
|
||||||
import { SECOND } from '@/const.js';
|
import { SECOND } from '@/const.js';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
|
|
||||||
export async function getJson(url: string, accept = 'application/json, */*', timeout = 10 * SECOND, headers: Record<string, string> = {}) {
|
export async function getJson(url: string, accept = 'application/json, */*', timeout = 10 * SECOND, headers?: Record<string, string>) {
|
||||||
const res = await getResponse({
|
const res = await getResponse({
|
||||||
url,
|
url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: Object.assign({
|
headers: Object.assign({
|
||||||
|
'User-Agent': config.userAgent,
|
||||||
Accept: accept,
|
Accept: accept,
|
||||||
}, headers),
|
}, headers || {}),
|
||||||
timeout,
|
timeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHtml(url: string, accept = 'text/html, */*', timeout = 10 * SECOND, headers: Record<string, string> = {}) {
|
export async function getHtml(url: string, accept = 'text/html, */*', timeout = 10 * SECOND, headers?: Record<string, string>) {
|
||||||
const res = await getResponse({
|
const res = await getResponse({
|
||||||
url,
|
url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: Object.assign({
|
headers: Object.assign({
|
||||||
|
'User-Agent': config.userAgent,
|
||||||
Accept: accept,
|
Accept: accept,
|
||||||
}, headers),
|
}, headers || {}),
|
||||||
timeout,
|
timeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await res.text();
|
return await res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResponse(_args: {
|
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number, redirect: 'follow' | 'manual' | 'error' = 'follow' }) {
|
||||||
url: string,
|
const timeout = args.timeout || 10 * SECOND;
|
||||||
method: string,
|
|
||||||
body?: string,
|
const controller = new AbortController();
|
||||||
headers: Record<string, string>,
|
setTimeout(() => {
|
||||||
timeout?: number,
|
controller.abort();
|
||||||
size?: number,
|
}, timeout * 6);
|
||||||
redirect?: 'follow' | 'manual' | 'error',
|
|
||||||
}) {
|
|
||||||
const args = {
|
|
||||||
timeout: 10 * SECOND,
|
|
||||||
size: 10 * 1024 * 1024, // 10 MiB
|
|
||||||
redirect: 'follow',
|
|
||||||
..._args,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(args.url, {
|
const res = await fetch(args.url, {
|
||||||
method: args.method,
|
method: args.method,
|
||||||
headers: Object.assign({
|
headers: args.headers,
|
||||||
'User-Agent': config.userAgent,
|
|
||||||
}, args.headers),
|
|
||||||
body: args.body,
|
body: args.body,
|
||||||
redirect: args.redirect,
|
redirect: args.redirect,
|
||||||
size: args.size,
|
timeout,
|
||||||
|
size: args.size || 10 * 1024 * 1024, // 10 MiB
|
||||||
agent: getAgentByUrl,
|
agent: getAgentByUrl,
|
||||||
signal: AbortSignal.timeout(args.timeout),
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -81,63 +71,13 @@ const cache = new CacheableLookup({
|
||||||
lookup: false, // nativeのdns.lookupにfallbackしない
|
lookup: false, // nativeのdns.lookupにfallbackしない
|
||||||
});
|
});
|
||||||
|
|
||||||
// because `cacheable-lookup` is annoying and prefers IPv4 by default,
|
|
||||||
// we need a little wrapper setting some extra options
|
|
||||||
const cacheLookup = (host, _2, _3) => {
|
|
||||||
// do all the weird parameter shenanigans that nodejs's dns.lookup does.
|
|
||||||
let options = {}, callback;
|
|
||||||
if (_3) {
|
|
||||||
options = _2;
|
|
||||||
if (typeof options === 'number') {
|
|
||||||
options = { family: options };
|
|
||||||
}
|
|
||||||
callback = _3;
|
|
||||||
} else {
|
|
||||||
callback = _2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// here come the shenanigans, trying to be careful not to mess up
|
|
||||||
// intentionally different behaviour
|
|
||||||
if (options.family == null && (options.hints ?? 0) === 0) {
|
|
||||||
options.family = 6;
|
|
||||||
options.hints = dns.V4MAPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback wrapper that checks whether an IP is private.
|
|
||||||
// Private IPs will not be returned in the first place.
|
|
||||||
const wrapper = (err, address, family) => {
|
|
||||||
// mimics dns.lookup behaviour as if no result was found
|
|
||||||
const fakeErr = new Error("private IP");
|
|
||||||
fakeErr.code = 'ENOTFOUND';
|
|
||||||
|
|
||||||
if (err != null) {
|
|
||||||
return callback(err, address, family);
|
|
||||||
} else if (options.all) {
|
|
||||||
const results = address.filter(({ address, family }) => isPublicIp(address));
|
|
||||||
if (results.length === 0) {
|
|
||||||
return callback(fakeErr);
|
|
||||||
} else {
|
|
||||||
return callback(err, results);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isPublicIp(address)) {
|
|
||||||
return callback(err, address, family);
|
|
||||||
} else {
|
|
||||||
return callback(fakeErr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return cache.lookup(host, options, wrapper);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get http non-proxy agent
|
* Get http non-proxy agent
|
||||||
*/
|
*/
|
||||||
const _http = new http.Agent({
|
const _http = new http.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * SECOND,
|
keepAliveMsecs: 30 * SECOND,
|
||||||
lookup: cacheLookup,
|
lookup: cache.lookup,
|
||||||
} as http.AgentOptions);
|
} as http.AgentOptions);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,7 +86,7 @@ const _http = new http.Agent({
|
||||||
const _https = new https.Agent({
|
const _https = new https.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * SECOND,
|
keepAliveMsecs: 30 * SECOND,
|
||||||
lookup: cacheLookup,
|
lookup: cache.lookup,
|
||||||
} as https.AgentOptions);
|
} as https.AgentOptions);
|
||||||
|
|
||||||
const maxSockets = Math.max(256, config.deliverJobConcurrency);
|
const maxSockets = Math.max(256, config.deliverJobConcurrency);
|
||||||
|
@ -205,27 +145,3 @@ export class StatusError extends Error {
|
||||||
this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
|
this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPublicIp(ip: string): boolean {
|
|
||||||
for (const net of config.allowedPrivateNetworks || []) {
|
|
||||||
const cidr = new IPCIDR(net);
|
|
||||||
if (cidr.contains(ip)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround because ipaddr.js in the installed version cannot cope
|
|
||||||
// with v4mapped addresses. a fix has been submitted and merged, but a new
|
|
||||||
// version was not yet released.
|
|
||||||
// FIXME: update ipaddr.js and remove workaround
|
|
||||||
ip = ip.replace(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i, '$1');
|
|
||||||
|
|
||||||
try {
|
|
||||||
let range = ipaddr.process(ip).range();
|
|
||||||
// only unicast or multicast addresses are allowed by default
|
|
||||||
// this does not include e.g. loopback addresses
|
|
||||||
return ['unicast', 'multicast'].includes(range);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,5 +37,14 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renoteのとき
|
||||||
|
if (note.renoteId) {
|
||||||
|
if (note.renote) {
|
||||||
|
summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
|
||||||
|
} else {
|
||||||
|
summary += '\n\nRN: ...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return summary.trim();
|
return summary.trim();
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,10 +2,9 @@ import { UserKeypairs } from '@/models/index.js';
|
||||||
import { User } from '@/models/entities/user.js';
|
import { User } from '@/models/entities/user.js';
|
||||||
import { UserKeypair } from '@/models/entities/user-keypair.js';
|
import { UserKeypair } from '@/models/entities/user-keypair.js';
|
||||||
import { Cache } from './cache.js';
|
import { Cache } from './cache.js';
|
||||||
import { MINUTE } from '@/const.js';
|
|
||||||
|
|
||||||
const cache = new Cache<UserKeypair>(
|
const cache = new Cache<UserKeypair>(
|
||||||
15 * MINUTE,
|
Infinity,
|
||||||
(userId) => UserKeypairs.findOneByOrFail({ userId }),
|
(userId) => UserKeypairs.findOneByOrFail({ userId }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,12 @@ function normalizeHost(src: string | undefined, noteUserHost: string | null): st
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
function parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
||||||
// emojiName may be of the form `emoji@host`, turn it into a suitable form
|
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
|
||||||
const match = emojiName.split("@");
|
if (!match) return { name: null, host: null };
|
||||||
const name = match[0];
|
|
||||||
const host = toPunyNullable(normalizeHost(match[1], noteUserHost));
|
const name = match[1];
|
||||||
|
|
||||||
|
const host = toPunyNullable(normalizeHost(match[2], noteUserHost));
|
||||||
|
|
||||||
return { name, host };
|
return { name, host };
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,11 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public langs: string[];
|
public langs: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public pinnedUsers: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, array: true, default: '{}',
|
length: 256, array: true, default: '{}',
|
||||||
})
|
})
|
||||||
|
@ -175,6 +180,12 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public remoteDriveCapacityMb: number;
|
public remoteDriveCapacityMb: number;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public summalyProxy: string | null;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,12 +19,6 @@ export class Note {
|
||||||
})
|
})
|
||||||
public createdAt: Date;
|
public createdAt: Date;
|
||||||
|
|
||||||
@Column('timestamp with time zone', {
|
|
||||||
nullable: true,
|
|
||||||
comment: 'The updated date of the Note.',
|
|
||||||
})
|
|
||||||
public updatedAt: Date | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
|
|
@ -34,6 +34,12 @@ export class Poll {
|
||||||
public votes: number[];
|
public votes: number[];
|
||||||
|
|
||||||
//#region Denormalized fields
|
//#region Denormalized fields
|
||||||
|
@Column('enum', {
|
||||||
|
enum: noteVisibilities,
|
||||||
|
comment: '[Denormalized]',
|
||||||
|
})
|
||||||
|
public noteVisibility: typeof noteVisibilities[number];
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
|
|
@ -169,7 +169,6 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||||
const packed: Packed<'Note'> = await awaitAll({
|
const packed: Packed<'Note'> = await awaitAll({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
createdAt: note.createdAt.toISOString(),
|
createdAt: note.createdAt.toISOString(),
|
||||||
updatedAt: note.updatedAt?.toISOString() ?? null,
|
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
user: Users.pack(note.user ?? note.userId, me, {
|
user: Users.pack(note.user ?? note.userId, me, {
|
||||||
detail: false,
|
detail: false,
|
||||||
|
|
|
@ -8,10 +8,14 @@ import { populateEmojis } from '@/misc/populate-emojis.js';
|
||||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD, HOUR } from '@/const.js';
|
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD, HOUR } from '@/const.js';
|
||||||
import { Cache } from '@/misc/cache.js';
|
import { Cache } from '@/misc/cache.js';
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
|
||||||
import { Instance } from '../entities/instance.js';
|
import { Instance } from '../entities/instance.js';
|
||||||
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, RenoteMutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
|
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, RenoteMutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
|
||||||
|
|
||||||
|
const userInstanceCache = new Cache<Instance | null>(
|
||||||
|
3 * HOUR,
|
||||||
|
(host) => Instances.findOneBy({ host }).then(x => x ?? undefined),
|
||||||
|
);
|
||||||
|
|
||||||
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
|
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
|
||||||
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
|
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
|
||||||
Detailed extends true ?
|
Detailed extends true ?
|
||||||
|
@ -298,10 +302,10 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
|
|
||||||
const ffVisible = await this.areFollowersVisibleTo(user, me);
|
const ffVisible = await this.areFollowersVisibleTo(user, me);
|
||||||
|
|
||||||
const followingCount = !opts.detail ? null :
|
const followingCount = opts.detail ? null :
|
||||||
ffVisible ? user.followingCount : null;
|
ffVisible ? user.followingCount : null;
|
||||||
|
|
||||||
const followersCount = !opts.detail ? null :
|
const followersCount = opts.detail ? null :
|
||||||
ffVisible ? user.followersCount : null;
|
ffVisible ? user.followersCount : null;
|
||||||
|
|
||||||
const packed = {
|
const packed = {
|
||||||
|
@ -315,7 +319,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
isModerator: user.isModerator,
|
isModerator: user.isModerator,
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
instance: !user.host ? undefined : registerOrFetchInstanceDoc(user.host)
|
instance: !user.host ? undefined : userInstanceCache.fetch(user.host)
|
||||||
.then(instance => !instance ? undefined : {
|
.then(instance => !instance ? undefined : {
|
||||||
name: instance.name,
|
name: instance.name,
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
|
|
|
@ -12,11 +12,6 @@ export const packedNoteSchema = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
format: 'date-time',
|
format: 'date-time',
|
||||||
},
|
},
|
||||||
updatedAt: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
format: 'date-time',
|
|
||||||
},
|
|
||||||
text: {
|
text: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
|
|
@ -32,6 +32,11 @@ export const packedUserLiteSchema = {
|
||||||
type: 'any',
|
type: 'any',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
|
avatarColor: {
|
||||||
|
type: 'any',
|
||||||
|
nullable: true, optional: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
isAdmin: {
|
isAdmin: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: true,
|
nullable: false, optional: true,
|
||||||
|
@ -117,6 +122,11 @@ export const packedUserDetailedNotMeOnlySchema = {
|
||||||
type: 'any',
|
type: 'any',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
|
bannerColor: {
|
||||||
|
type: 'any',
|
||||||
|
nullable: true, optional: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
isLocked: {
|
isLocked: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
|
|
|
@ -58,16 +58,8 @@ deliverQueue
|
||||||
await deletionRefCount(job);
|
await deletionRefCount(job);
|
||||||
})
|
})
|
||||||
.on('failed', async (job, err) => {
|
.on('failed', async (job, err) => {
|
||||||
if (err.type === 'aborted') {
|
|
||||||
deliverLogger.debug(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`);
|
|
||||||
} else {
|
|
||||||
deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`);
|
deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`);
|
||||||
}
|
|
||||||
|
|
||||||
if (job.attemptsMade >= (job.opts?.attempts ?? 1)) {
|
|
||||||
// this was the last attempt
|
|
||||||
await deletionRefCount(job);
|
await deletionRefCount(job);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`))
|
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`))
|
||||||
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||||
|
@ -310,22 +302,6 @@ export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createUpdateInstanceJob(host: string) {
|
|
||||||
return systemQueue.add(
|
|
||||||
'updateInstance',
|
|
||||||
{ host },
|
|
||||||
{
|
|
||||||
delay: 5 * MINUTE,
|
|
||||||
// The docs say that trying to add a job with an ID that already exists will
|
|
||||||
// not actually add the job. This is useful to not duplicate jobs when parallel
|
|
||||||
// jobs request an update.
|
|
||||||
jobId: host,
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: 10,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
deliverQueue.process(config.deliverJobConcurrency, processDeliver);
|
deliverQueue.process(config.deliverJobConcurrency, processDeliver);
|
||||||
inboxQueue.process(config.inboxJobConcurrency, processInbox);
|
inboxQueue.process(config.inboxJobConcurrency, processInbox);
|
||||||
|
|
|
@ -23,19 +23,6 @@ export function initialize<T>(name: string, limitPerSec = -1): Bull.Queue<T> {
|
||||||
function apBackoff(attemptsMade: number /*, err: Error */): number {
|
function apBackoff(attemptsMade: number /*, err: Error */): number {
|
||||||
const baseDelay = MINUTE;
|
const baseDelay = MINUTE;
|
||||||
const maxBackoff = 8 * HOUR;
|
const maxBackoff = 8 * HOUR;
|
||||||
/*
|
|
||||||
attempt | average seconds + up to 2% random offset
|
|
||||||
0 | 0
|
|
||||||
1 | 60 = 1min
|
|
||||||
2 | 180 = 3min
|
|
||||||
3 | 420 = 7min
|
|
||||||
4 | 900 = 15min
|
|
||||||
5 | 1860 = 31min
|
|
||||||
6 | 3780 = 63min
|
|
||||||
7 | 7620 = 127min ~= 2.1h
|
|
||||||
8 | 15300 = 4.25h
|
|
||||||
>8 | 28800 = 8h
|
|
||||||
*/
|
|
||||||
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
|
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
|
||||||
backoff = Math.min(backoff, maxBackoff);
|
backoff = Math.min(backoff, maxBackoff);
|
||||||
backoff += Math.round(backoff * Math.random() * 0.2);
|
backoff += Math.round(backoff * Math.random() * 0.2);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
|
|
||||||
export const queueLogger = new Logger('queue');
|
export const queueLogger = new Logger('queue', 'orange');
|
||||||
|
|
|
@ -58,10 +58,12 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const emoji of customEmojis) {
|
for (const emoji of customEmojis) {
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(emoji.name)) {
|
||||||
|
this.logger.error(`invalid emoji name: ${emoji.name}, skipping in emoji export`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const ext = mime.extension(emoji.type);
|
const ext = mime.extension(emoji.type);
|
||||||
// there are some restrictions on file names, so to be safe the files are
|
const fileName = emoji.name + (ext ? '.' + ext : '');
|
||||||
// named after their database id instead of the actual emoji name
|
|
||||||
const fileName = emoji.id + (ext ? '.' + ext : '');
|
|
||||||
const emojiPath = path + '/' + fileName;
|
const emojiPath = path + '/' + fileName;
|
||||||
fs.writeFileSync(emojiPath, '', 'binary');
|
fs.writeFileSync(emojiPath, '', 'binary');
|
||||||
let downloaded = false;
|
let downloaded = false;
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import Bull from 'bull';
|
import Bull from 'bull';
|
||||||
import decompress from 'decompress';
|
import unzipper from 'unzipper';
|
||||||
|
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import { createTempDir } from '@/misc/create-temp.js';
|
import { createTempDir } from '@/misc/create-temp.js';
|
||||||
|
import { downloadUrl } from '@/misc/download-url.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import { DriveFiles, Emojis } from '@/models/index.js';
|
import { DriveFiles, Emojis } from '@/models/index.js';
|
||||||
import { DbUserImportJobData } from '@/queue/types.js';
|
import { DbUserImportJobData } from '@/queue/types.js';
|
||||||
import { queueLogger } from '@/queue/logger.js';
|
import { queueLogger } from '@/queue/logger.js';
|
||||||
import { addFile } from '@/services/drive/add-file.js';
|
import { addFile } from '@/services/drive/add-file.js';
|
||||||
import { copyFileTo } from '@/services/drive/read-file.js';
|
|
||||||
|
|
||||||
const logger = queueLogger.createSubLogger('import-custom-emojis');
|
const logger = queueLogger.createSubLogger('import-custom-emojis');
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(destPath, '', 'binary');
|
fs.writeFileSync(destPath, '', 'binary');
|
||||||
await copyFileTo(file, destPath);
|
await downloadUrl(file.url, destPath);
|
||||||
} catch (e) { // TODO: 何度か再試行
|
} catch (e) { // TODO: 何度か再試行
|
||||||
if (e instanceof Error || typeof e === 'string') {
|
if (e instanceof Error || typeof e === 'string') {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
@ -42,9 +42,9 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputPath = path + '/emojis';
|
const outputPath = path + '/emojis';
|
||||||
logger.succ(`Unzipping to ${outputPath}`);
|
const unzipStream = fs.createReadStream(destPath);
|
||||||
await decompress(destPath, outputPath);
|
const extractor = unzipper.Extract({ path: outputPath });
|
||||||
|
extractor.on('close', async () => {
|
||||||
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
|
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
|
||||||
const meta = JSON.parse(metaRaw);
|
const meta = JSON.parse(metaRaw);
|
||||||
|
|
||||||
|
@ -73,10 +73,13 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.queryResultCache?.remove(['meta_emojis']);
|
await db.queryResultCache!.remove(['meta_emojis']);
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
logger.succ('Imported');
|
logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
|
unzipStream.pipe(extractor);
|
||||||
|
logger.succ(`Unzipping to ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,40 +2,29 @@ import { URL } from 'node:url';
|
||||||
import Bull from 'bull';
|
import Bull from 'bull';
|
||||||
import { request } from '@/remote/activitypub/request.js';
|
import { request } from '@/remote/activitypub/request.js';
|
||||||
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
||||||
|
import Logger from '@/services/logger.js';
|
||||||
import { Instances } from '@/models/index.js';
|
import { Instances } from '@/models/index.js';
|
||||||
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
||||||
import { toPuny } from '@/misc/convert-host.js';
|
import { toPuny } from '@/misc/convert-host.js';
|
||||||
import { StatusError } from '@/misc/fetch.js';
|
import { StatusError } from '@/misc/fetch.js';
|
||||||
import { getUserKeypair } from '@/misc/keypair-store.js';
|
|
||||||
import { shouldSkipInstance } from '@/misc/skipped-instances.js';
|
import { shouldSkipInstance } from '@/misc/skipped-instances.js';
|
||||||
import { DeliverJobData } from '@/queue/types.js';
|
import { DeliverJobData } from '@/queue/types.js';
|
||||||
|
|
||||||
|
const logger = new Logger('deliver');
|
||||||
|
|
||||||
export default async (job: Bull.Job<DeliverJobData>) => {
|
export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
const { host } = new URL(job.data.to);
|
const { host } = new URL(job.data.to);
|
||||||
const puny = toPuny(host);
|
const puny = toPuny(host);
|
||||||
|
|
||||||
// for the first few tries (where most attempts will be made)
|
if (await shouldSkipInstance(puny)) return 'skip';
|
||||||
// we assume that inserting deliver jobs took care of this check
|
|
||||||
// only on later attempts do we actually do it, to ease database
|
|
||||||
// performance. this might cause a slight delay of a few minutes
|
|
||||||
// for instance blocks being applied
|
|
||||||
//
|
|
||||||
// with apBackoff, attempt 2 happens ~4min after the initial try, while
|
|
||||||
// attempt 3 happens ~11 min after the initial try, which seems like a
|
|
||||||
// good tradeoff between database and blocks being applied reasonably quick
|
|
||||||
if (job.attemptsMade >= 3 && await shouldSkipInstance(puny)) {
|
|
||||||
return 'skip';
|
|
||||||
}
|
|
||||||
|
|
||||||
const keypair = await getUserKeypair(job.data.user.id);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Array.isArray(job.data.content)) {
|
if (Array.isArray(job.data.content)) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
job.data.content.map(x => request(job.data.user, job.data.to, x, keypair))
|
job.data.content.map(x => request(job.data.user, job.data.to, x))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await request(job.data.user, job.data.to, job.data.content, keypair);
|
await request(job.data.user, job.data.to, job.data.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats
|
// Update stats
|
||||||
|
@ -47,7 +36,7 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
isNotResponding: false,
|
isNotResponding: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchInstanceMetadata(host);
|
fetchInstanceMetadata(i);
|
||||||
});
|
});
|
||||||
} catch (res) {
|
} catch (res) {
|
||||||
// Update stats
|
// Update stats
|
||||||
|
|
|
@ -2,8 +2,6 @@ import Bull from 'bull';
|
||||||
import { Notes, PollVotes } from '@/models/index.js';
|
import { Notes, PollVotes } from '@/models/index.js';
|
||||||
import { EndedPollNotificationJobData } from '@/queue/types.js';
|
import { EndedPollNotificationJobData } from '@/queue/types.js';
|
||||||
import { createNotification } from '@/services/create-notification.js';
|
import { createNotification } from '@/services/create-notification.js';
|
||||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
|
||||||
import { updateQuestion } from '@/remote/activitypub/models/question.js';
|
|
||||||
|
|
||||||
export async function endedPollNotification(job: Bull.Job<EndedPollNotificationJobData>, done: any): Promise<void> {
|
export async function endedPollNotification(job: Bull.Job<EndedPollNotificationJobData>, done: any): Promise<void> {
|
||||||
const note = await Notes.findOneBy({ id: job.data.noteId });
|
const note = await Notes.findOneBy({ id: job.data.noteId });
|
||||||
|
@ -12,9 +10,6 @@ export async function endedPollNotification(job: Bull.Job<EndedPollNotificationJ
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the (final) numbers of votes
|
|
||||||
await updateQuestion(note.uri, new Resolver());
|
|
||||||
|
|
||||||
const votes = await PollVotes.createQueryBuilder('vote')
|
const votes = await PollVotes.createQueryBuilder('vote')
|
||||||
.select('vote.userId')
|
.select('vote.userId')
|
||||||
.where('vote.noteId = :noteId', { noteId: note.id })
|
.where('vote.noteId = :noteId', { noteId: note.id })
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import Bull from 'bull';
|
import Bull from 'bull';
|
||||||
|
import httpSignature from '@peertube/http-signature';
|
||||||
import { perform } from '@/remote/activitypub/perform.js';
|
import { perform } from '@/remote/activitypub/perform.js';
|
||||||
import Logger from '@/services/logger.js';
|
import Logger from '@/services/logger.js';
|
||||||
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
||||||
import { Instances } from '@/models/index.js';
|
import { Instances } from '@/models/index.js';
|
||||||
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
||||||
import { extractPunyHost } from '@/misc/convert-host.js';
|
import { toPuny, extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { getApId } from '@/remote/activitypub/type.js';
|
import { getApId } from '@/remote/activitypub/type.js';
|
||||||
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
||||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||||
import { AuthUser } from '@/remote/activitypub/misc/auth-user.js';
|
import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js';
|
||||||
|
import { getAuthUser } from '@/remote/activitypub/misc/auth-user.js';
|
||||||
|
import { StatusError } from '@/misc/fetch.js';
|
||||||
import { InboxJobData } from '@/queue/types.js';
|
import { InboxJobData } from '@/queue/types.js';
|
||||||
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
||||||
import { verifyHttpSignature } from '@/remote/http-signature.js';
|
|
||||||
|
|
||||||
const logger = new Logger('inbox');
|
const logger = new Logger('inbox');
|
||||||
|
|
||||||
// Processing when an activity arrives in the user's inbox
|
// ユーザーのinboxにアクティビティが届いた時の処理
|
||||||
export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
const signature = job.data.signature; // HTTP-signature
|
const signature = job.data.signature; // HTTP-signature
|
||||||
const activity = job.data.activity;
|
const activity = job.data.activity;
|
||||||
const resolver = new Resolver();
|
|
||||||
|
|
||||||
//#region Log
|
//#region Log
|
||||||
const info = Object.assign({}, activity) as any;
|
const info = Object.assign({}, activity) as any;
|
||||||
|
@ -28,29 +29,93 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
logger.debug(JSON.stringify(info, null, 2));
|
logger.debug(JSON.stringify(info, null, 2));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const validated = await verifyHttpSignature(signature, resolver, getApId(activity.actor));
|
const keyIdLower = signature.keyId.toLowerCase();
|
||||||
let authUser = validated.authUser;
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
|
return `Old keyId is no longer supported. ${keyIdLower}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = toPuny(new URL(keyIdLower).hostname);
|
||||||
|
|
||||||
|
// Stop if the host is blocked.
|
||||||
|
if (await shouldBlockInstance(host)) {
|
||||||
|
return `Blocked request: ${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolver = new Resolver();
|
||||||
|
|
||||||
|
let authUser;
|
||||||
|
try {
|
||||||
|
authUser = await getAuthUser(signature.keyId, getApId(activity.actor), resolver);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof StatusError) {
|
||||||
|
if (e.isClientError) {
|
||||||
|
return `skip: Ignored deleted actors on both ends ${activity.actor} - ${e.statusCode}`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Error in actor ${activity.actor} - ${e.statusCode || e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authUser == null) {
|
||||||
|
// Key not found? Unacceptable!
|
||||||
|
return 'skip: failed to resolve user';
|
||||||
|
} else {
|
||||||
|
// Found key!
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the HTTP Signature
|
||||||
|
const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
|
||||||
|
|
||||||
// The signature must be valid.
|
// The signature must be valid.
|
||||||
// The signature must also match the actor otherwise anyone could sign any activity.
|
// The signature must also match the actor otherwise anyone could sign any activity.
|
||||||
if (validated.status !== 'valid' || validated.authUser.user.uri !== activity.actor) {
|
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||||
return `skip: http-signature verification failed. keyId=${signature.keyId}`;
|
// Last resort: LD-Signature
|
||||||
|
if (activity.signature) {
|
||||||
|
if (activity.signature.type !== 'RsaSignature2017') {
|
||||||
|
return `skip: unsupported LD-signature type ${activity.signature.type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// authUser cannot be null at this point:
|
// get user based on LD-Signature key id.
|
||||||
// either it was already not null because the HTTP signature was valid
|
// lets assume that the creator has this common form:
|
||||||
// or, if the LD signature was not verified, this function will already have returned.
|
// <https://example.com/users/user#main-key>
|
||||||
authUser = authUser as AuthUser;
|
// Then we can use it as the key id and (without fragment part) user id.
|
||||||
|
authUser = await getAuthUser(activity.signature.creator, activity.signature.creator.replace(/#.*$/, ''), resolver);
|
||||||
|
|
||||||
|
if (authUser == null) {
|
||||||
|
return 'skip: failed to resolve LD-Signature user';
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD-Signature verification
|
||||||
|
const ldSignature = new LdSignature();
|
||||||
|
const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
||||||
|
if (!verified) {
|
||||||
|
return 'skip: LD-Signatureの検証に失敗しました';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Again, the actor must match.
|
||||||
|
if (authUser.user.uri !== activity.actor) {
|
||||||
|
return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop if the host is blocked.
|
||||||
|
const ldHost = extractDbHost(authUser.user.uri);
|
||||||
|
if (await shouldBlockInstance(ldHost)) {
|
||||||
|
return `Blocked request: ${ldHost}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the actor's host is not blocked
|
// Verify that the actor's host is not blocked
|
||||||
const signerHost = extractPunyHost(authUser.user.uri!);
|
const signerHost = extractDbHost(authUser.user.uri!);
|
||||||
if (await shouldBlockInstance(signerHost)) {
|
if (await shouldBlockInstance(signerHost)) {
|
||||||
return `Blocked request: ${signerHost}`;
|
return `Blocked request: ${signerHost}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof activity.id === 'string') {
|
if (typeof activity.id === 'string') {
|
||||||
// Verify that activity and actor are from the same host.
|
// Verify that activity and actor are from the same host.
|
||||||
const activityIdHost = extractPunyHost(activity.id);
|
const activityIdHost = extractDbHost(activity.id);
|
||||||
if (signerHost !== activityIdHost) {
|
if (signerHost !== activityIdHost) {
|
||||||
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
|
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +134,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
isNotResponding: false,
|
isNotResponding: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchInstanceMetadata(i.host);
|
fetchInstanceMetadata(i);
|
||||||
|
|
||||||
instanceChart.requestReceived(i.host);
|
instanceChart.requestReceived(i.host);
|
||||||
apRequestChart.inbox();
|
apRequestChart.inbox();
|
||||||
|
|
|
@ -45,6 +45,6 @@ export default async function cleanRemoteFiles(job: Bull.Job<Record<string, unkn
|
||||||
job.progress(deletedCount / total);
|
job.progress(deletedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.succ('All cached remote files have been deleted.');
|
logger.succ('All cahced remote files has been deleted.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,12 @@ const logger = queueLogger.createSubLogger('check-expired');
|
||||||
export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
||||||
logger.info('Checking expired data...');
|
logger.info('Checking expired data...');
|
||||||
|
|
||||||
const OlderThan = (millis: number) => {
|
const expiredMutings = await Mutings.createQueryBuilder('muting')
|
||||||
return LessThan(new Date(new Date().getTime() - millis));
|
|
||||||
};
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
Mutings.createQueryBuilder('muting')
|
|
||||||
.where('muting.expiresAt IS NOT NULL')
|
.where('muting.expiresAt IS NOT NULL')
|
||||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||||
.getMany()
|
.getMany();
|
||||||
.then(async (expiredMutings) => {
|
|
||||||
if (expiredMutings.length > 0) {
|
if (expiredMutings.length > 0) {
|
||||||
await Mutings.delete({
|
await Mutings.delete({
|
||||||
id: In(expiredMutings.map(m => m.id)),
|
id: In(expiredMutings.map(m => m.id)),
|
||||||
|
@ -30,29 +25,37 @@ export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done:
|
||||||
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
Signins.delete({
|
const OlderThan = (millis: number) => {
|
||||||
|
return LessThan(new Date(new Date().getTime() - millis));
|
||||||
|
};
|
||||||
|
|
||||||
|
await Signins.delete({
|
||||||
createdAt: OlderThan(2 * MONTH),
|
createdAt: OlderThan(2 * MONTH),
|
||||||
}),
|
});
|
||||||
AttestationChallenges.delete({
|
|
||||||
|
await AttestationChallenges.delete({
|
||||||
createdAt: OlderThan(5 * MINUTE),
|
createdAt: OlderThan(5 * MINUTE),
|
||||||
}),
|
});
|
||||||
PasswordResetRequests.delete({
|
|
||||||
|
await PasswordResetRequests.delete({
|
||||||
// this timing should be the same as in @/server/api/endpoints/reset-password.ts
|
// this timing should be the same as in @/server/api/endpoints/reset-password.ts
|
||||||
createdAt: OlderThan(30 * MINUTE),
|
createdAt: OlderThan(30 * MINUTE),
|
||||||
}),
|
});
|
||||||
AuthSessions.delete({
|
|
||||||
|
await AuthSessions.delete({
|
||||||
createdAt: OlderThan(15 * MINUTE),
|
createdAt: OlderThan(15 * MINUTE),
|
||||||
}),
|
});
|
||||||
Notifications.delete({
|
|
||||||
|
await Notifications.delete({
|
||||||
isRead: true,
|
isRead: true,
|
||||||
createdAt: OlderThan(3 * MONTH),
|
createdAt: OlderThan(3 * MONTH),
|
||||||
}),
|
});
|
||||||
Users.delete({
|
|
||||||
|
await Users.delete({
|
||||||
// delete users where the deletion status reference count has come down to zero
|
// delete users where the deletion status reference count has come down to zero
|
||||||
isDeleted: 0,
|
isDeleted: 0,
|
||||||
}),
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
logger.succ('Deleted expired data.');
|
logger.succ('Deleted expired data.');
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue