Compare commits

..

2 Commits

50 changed files with 1314 additions and 2802 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ If applicable, add screenshots to help explain your problem.
**Bug occurs on official PairDrop instance https://pairdrop.net/** **Bug occurs on official PairDrop instance https://pairdrop.net/**
No | Yes No | Yes
Version: v1.10.9 Version: v1.10.6
**Bug occurs on self-hosted PairDrop instance** **Bug occurs on self-hosted PairDrop instance**
No | Yes No | Yes
@@ -44,7 +44,7 @@ No | Yes
**Self-Hosted Setup** **Self-Hosted Setup**
Proxy: Nginx | Apache2 Proxy: Nginx | Apache2
Deployment: docker run | docker compose | npm run start:prod Deployment: docker run | docker compose | npm run start:prod
Version: v1.10.9 Version: v1.10.6
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.
+2 -1
View File
@@ -1,6 +1,7 @@
node_modules node_modules
.DS_Store .DS_Store
/dev/certs fqdn.env
/docker/certs
qrcode-svg/ qrcode-svg/
turnserver.conf turnserver.conf
rtc_config.json rtc_config.json
+4 -3
View File
@@ -39,6 +39,7 @@ Send a file from your phone to your laptop?
* Connect to devices in complex network environments (public Wi-Fi, company network, iCloud Private Relay, VPN, etc.). * Connect to devices in complex network environments (public Wi-Fi, company network, iCloud Private Relay, VPN, etc.).
* Connect to devices on your mobile hotspot. * Connect to devices on your mobile hotspot.
* Devices outside of your local network that are behind a NAT are auto-connected via the PairDrop TURN server. * Devices outside of your local network that are behind a NAT are auto-connected via the PairDrop TURN server.
* Connect to devices on your mobile hotspot.
* Devices from the local network, in the same public room, or previously paired are shown. * Devices from the local network, in the same public room, or previously paired are shown.
#### Persistent Device Pairing #### Persistent Device Pairing
@@ -118,9 +119,9 @@ Connect to others in complex network situations, or over the Internet.
<br /> <br />
PairDrop is libre, and always will be. \ PairDrop is libre, and always will be. \
If you find it useful and want to support free and open-source software, please consider donating using the button above. \ I footed the bill for the domain and the server, and you can help create great softeare by supporting me. \
I footed the bill for the domain and the server, and you can help create and maintain great software by supporting me. \ Please use BuyMeACoffee via the button above. \
Thank you very much for your contribution! Thanks a lot for supporting copylefted libre software!
## Contributing ## Contributing
Feel free to [open an issue](https://github.com/schlagmichdoch/pairdrop/issues/new/choose) or a Feel free to [open an issue](https://github.com/schlagmichdoch/pairdrop/issues/new/choose) or a
-3
View File
@@ -1,3 +0,0 @@
FROM nginx:alpine
RUN apk add --no-cache openssl
-41
View File
@@ -1,41 +0,0 @@
server {
listen 80;
expires epoch;
location / {
proxy_connect_timeout 300;
proxy_pass http://pairdrop:3000;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
}
location /ca.crt {
alias /etc/ssl/certs/pairdropCA.crt;
}
# To allow POST on static pages
error_page 405 =200 $uri;
}
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/pairdrop-dev.crt;
ssl_certificate_key /etc/ssl/certs/pairdrop-dev.key;
expires epoch;
location / {
proxy_connect_timeout 300;
proxy_pass http://pairdrop:3000;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
}
location /ca.crt {
alias /etc/ssl/certs/pairdropCA.crt;
}
# To allow POST on static pages
error_page 405 =200 $uri;
}
-9
View File
@@ -1,9 +0,0 @@
#!/bin/sh
cnf_dir='/mnt/openssl/'
certs_dir='/etc/ssl/certs/'
openssl req -config ${cnf_dir}pairdropCA.cnf -new -x509 -days 1 -keyout ${certs_dir}pairdropCA.key -out ${certs_dir}pairdropCA.crt
openssl req -config ${cnf_dir}pairdropCert.cnf -new -out /tmp/pairdrop-dev.csr -keyout ${certs_dir}pairdrop-dev.key
openssl x509 -req -in /tmp/pairdrop-dev.csr -CA ${certs_dir}pairdropCA.crt -CAkey ${certs_dir}pairdropCA.key -CAcreateserial -extensions req_ext -extfile ${cnf_dir}pairdropCert.cnf -sha512 -days 1 -out ${certs_dir}pairdrop-dev.crt
exec "$@"
-26
View File
@@ -1,26 +0,0 @@
[ req ]
default_bits = 2048
default_md = sha256
default_days = 1
encrypt_key = no
distinguished_name = subject
x509_extensions = x509_ext
string_mask = utf8only
prompt = no
[ subject ]
organizationName = PairDrop
OU = CA
commonName = pairdrop-CA
[ x509_ext ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
# You only need digitalSignature below. *If* you don't allow
# RSA Key transport (i.e., you use ephemeral cipher suites), then
# omit keyEncipherment because that's key transport.
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, digitalSignature, keyEncipherment, cRLSign, keyCertSign
-29
View File
@@ -1,29 +0,0 @@
[ req ]
default_bits = 2048
default_md = sha256
default_days = 1
encrypt_key = no
distinguished_name = subject
req_extensions = req_ext
string_mask = utf8only
prompt = no
[ subject ]
organizationName = PairDrop
OU = Development
# Use a friendly name here because it's presented to the user. The server's DNS
# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated
# by both IETF and CA/Browser Forums. If you place a DNS name here, then you
# must include the DNS name in the SAN too (otherwise, Chrome and others that
# strictly follow the CA/Browser Baseline Requirements will fail).
commonName = ${ENV::FQDN}
[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = DNS:${ENV::FQDN}
nsComment = "OpenSSL Generated Certificate"
extendedKeyUsage = serverAuth
-34
View File
@@ -1,34 +0,0 @@
version: "3"
services:
pairdrop:
build: .
container_name: pairdrop
restart: unless-stopped
environment:
- PUID=1000 # UID to run the application as
- PGID=1000 # GID to run the application as
- WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client.
- RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min.
- RTC_CONFIG=false # Set to the path of a file that specifies the STUN/TURN servers.
- DEBUG_MODE=false # Set to true to debug container and peer connections.
- TZ=Etc/UTC # Time Zone
ports:
- "127.0.0.1:3000:3000" # Web UI. Change the port number before the last colon e.g. `127.0.0.1:9000:3000`
nginx:
build:
context: dev/
dockerfile: nginx-with-openssl.Dockerfile
image: "nginx-with-openssl"
volumes:
- ./public:/usr/share/nginx/html
- ./dev/certs:/etc/ssl/certs
- ./dev/openssl:/mnt/openssl
- ./dev/nginx/default.conf:/etc/nginx/conf.d/default.conf
ports:
- "8080:80"
- "8443:443"
environment:
- FQDN=localhost
entrypoint: /mnt/openssl/create.sh
command: ["nginx", "-g", "daemon off;"]
restart: unless-stopped
+16 -74
View File
@@ -5,39 +5,18 @@
Help! I can't install the PWA! Help! I can't install the PWA!
</summary> </summary>
<br> if you are using a Chromium-based browser (Chrome, Edge, Vivaldi, Brave, etc.), you can easily install PairDrop PWA on your desktop
by clicking the install-button in the top-right corner while on [pairdrop.net](https://pairdrop.net).
Here is a good guide on how to install PWAs on different platforms: \
https://www.cdc.gov/niosh/mining/content/hearingloss/installPWA.html
**Chromium-based browser on Desktop (Chrome, Edge, Vivaldi, Brave, etc.)** \
Easily install PairDrop PWA on your desktop by clicking the install-button in the top-right corner while on [pairdrop.net](https://pairdrop.net).
<img width="400" src="pwa-install.png" alt="Example on how to install a pwa with Edge"> <img width="400" src="pwa-install.png" alt="Example on how to install a pwa with Edge">
**Desktop Firefox** \
On Firefox, PWAs are installable via [this browser extensions](https://addons.mozilla.org/de/firefox/addon/pwas-for-firefox/) On Firefox, PWAs are installable via [this browser extensions](https://addons.mozilla.org/de/firefox/addon/pwas-for-firefox/)
**Android** \
PWAs are installable only by using Google Chrome or Samsung Browser:
1. Visit [pairdrop.net](https://pairdrop.net)
2. Click _Install_ on the installation pop-up or use the three-dot-menu and click on _Add to Home screen_
3. Click _Add_ on the pop-up
**iOS** \
PWAs are installable only by using Safari:
1. Visit [pairdrop.net](https://pairdrop.net)
2. Click on the share icon
3. Click _Add to Home Screen_
4. Click _Add_ in the top right corner
<br> <br>
**Self-Hosted Instance?** \ <b>Self-Hosted Instance?</b>
To be able to install the PWA from a self-hosted instance, the connection needs to be [established through HTTPS](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs). To be able to install the PWA from a self-hosted instance, the connection needs to be [established through HTTPS](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs).
See [this host your own section](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#testing-pwa-related-features) for more info. See [this host your own section](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#testing-pwa-related-features) for more info.
<br> <br>
</details> </details>
@@ -47,9 +26,7 @@ See [this host your own section](https://github.com/schlagmichdoch/PairDrop/blob
Shortcuts? Shortcuts?
</summary> </summary>
<br> Shortcuts
Available shortcuts:
- Send a message with `CTRL + ENTER` - Send a message with `CTRL + ENTER`
- Close all "Send" and "Pair" dialogs by pressing `Esc`. - Close all "Send" and "Pair" dialogs by pressing `Esc`.
- Copy a received message to the clipboard with `CTRL/⌘ + C`. - Copy a received message to the clipboard with `CTRL/⌘ + C`.
@@ -63,20 +40,12 @@ Available shortcuts:
How to save images directly to the gallery on iOS? How to save images directly to the gallery on iOS?
</summary> </summary>
<br> Apparently, iOS does not allow images shared from a website to be saved to the gallery directly.
It simply does not offer that option for images shared from a website.
~~Apparently, iOS does not allow images shared from a website to be saved to the gallery directly.~~ iOS Shortcuts saves the day:
~~It simply does not offer that option for images shared from a website.~~
~~iOS Shortcuts saves the day:~~ \
I created a simple iOS shortcut that takes your photos and saves them to your gallery: I created a simple iOS shortcut that takes your photos and saves them to your gallery:
https://routinehub.co/shortcut/13988/ https://routinehub.co/shortcut/13988/
Update: \
Apparently, this was only a bug that is fixed in recent iOS version (https://github.com/WebKit/WebKit/pull/13111). \
If you use an older affected iOS version this might still be of use. \
Luckily, you can now simply use `Save Image`/`Save X Images` 🎉
<br> <br>
</details> </details>
@@ -86,13 +55,10 @@ Luckily, you can now simply use `Save Image`/`Save X Images` 🎉
Is it possible to send files or text directly from the "Context" or "Share" menu? Is it possible to send files or text directly from the "Context" or "Share" menu?
</summary> </summary>
<br>
Yes, it finally is. Yes, it finally is.
* [Send files directly from the "Context" menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows) * [Send files directly from the "Context" menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows)
* [Send directly from the "Share" menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios) * [Send directly from the "Share" menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
* [Send directly from the "Share" menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android) * [Send directly from the "Share" menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
<br> <br>
</details> </details>
@@ -102,12 +68,9 @@ Yes, it finally is.
Is it possible to send files or text directly via CLI? Is it possible to send files or text directly via CLI?
</summary> </summary>
<br>
Yes. Yes.
* [Send directly from a command-line interface](/docs/how-to.md#send-directly-via-command-line-interface) * [Send directly from a command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
<br> <br>
</details> </details>
@@ -117,14 +80,11 @@ Yes.
Are there any third-party Apps? Are there any third-party Apps?
</summary> </summary>
<br>
These third-party apps are compatible with PairDrop: These third-party apps are compatible with PairDrop:
1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android) 1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android)
2. [Snapdrop for Firefox (Addon)](https://github.com/ueen/SnapdropFirefoxAddon) 2. [Snapdrop for Firefox (Addon)](https://github.com/ueen/SnapdropFirefoxAddon)
3. Feel free to make one :) 3. Feel free to make one :)
<br> <br>
</details> </details>
@@ -134,8 +94,6 @@ These third-party apps are compatible with PairDrop:
What about the connection? Is it a P2P connection directly from device to device or is there any third-party-server? What about the connection? Is it a P2P connection directly from device to device or is there any third-party-server?
</summary> </summary>
<br>
It uses a WebRTC peer-to-peer connection. It uses a WebRTC peer-to-peer connection.
WebRTC needs a signaling server that is only used to establish a connection. WebRTC needs a signaling server that is only used to establish a connection.
The server is not involved in the file transfer. The server is not involved in the file transfer.
@@ -151,7 +109,6 @@ to learn more about STUN, TURN and WebRTC.
If you host your own instance If you host your own instance
and want to support devices that do not support WebRTC, and want to support devices that do not support WebRTC,
you can [start the PairDrop instance with an activated WebSocket fallback](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#websocket-fallback-for-vpn). you can [start the PairDrop instance with an activated WebSocket fallback](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#websocket-fallback-for-vpn).
<br> <br>
</details> </details>
@@ -161,11 +118,9 @@ you can [start the PairDrop instance with an activated WebSocket fallback](https
What about privacy? Will files be saved on third-party servers? What about privacy? Will files be saved on third-party servers?
</summary> </summary>
<br>
Files are sent directly between peers. Files are sent directly between peers.
PairDrop doesn't even use a database. PairDrop doesn't even use a database.
If curious, study [the signaling server](https://github.com/schlagmichdoch/PairDrop/blob/master/server/ws-server.js). If curious, study [the server](https://github.com/schlagmichdoch/pairdrop/blob/master/index.js).
WebRTC encrypts the files in transit. WebRTC encrypts the files in transit.
If the devices are on the same network, If the devices are on the same network,
@@ -175,7 +130,6 @@ If your devices are paired and behind a NAT,
the PairDrop TURN Server is used to route your files and messages. the PairDrop TURN Server is used to route your files and messages.
See the [Technical Documentation](technical-documentation.md#encryption-webrtc-stun-and-turn) See the [Technical Documentation](technical-documentation.md#encryption-webrtc-stun-and-turn)
to learn more about STUN, TURN and WebRTC. to learn more about STUN, TURN and WebRTC.
<br> <br>
</details> </details>
@@ -185,12 +139,10 @@ to learn more about STUN, TURN and WebRTC.
What about security? Are my files encrypted while sent between the computers? What about security? Are my files encrypted while sent between the computers?
</summary> </summary>
<br>
Yes. Your files are sent using WebRTC, encrypting them in transit. Yes. Your files are sent using WebRTC, encrypting them in transit.
Still you have to trust the PairDrop server. To ensure the connection is secure and there is no [MITM](https://wikiless.org/wiki/Man-in-the-middle_attack) there is a plan to make PairDrop To ensure the connection is secure and there is no [MITM](https://wikiless.org/wiki/Man-in-the-middle_attack),
zero trust by encrypting the signaling and implementing a verification process. See [issue #180](https://github.com/schlagmichdoch/PairDrop/issues/180) to keep updated. compare the security number shown under the device name on both devices.
The security number is different for every connection.
<br> <br>
</details> </details>
@@ -200,8 +152,6 @@ zero trust by encrypting the signaling and implementing a verification process.
Transferring many files with paired devices takes too long Transferring many files with paired devices takes too long
</summary> </summary>
<br>
Naturally, if traffic needs to be routed through the TURN server Naturally, if traffic needs to be routed through the TURN server
because your devices are behind different NATs, transfer speed decreases. because your devices are behind different NATs, transfer speed decreases.
@@ -214,7 +164,6 @@ which omits the need of the TURN server.
You can also use mobile hotspots on phones to do that. You can also use mobile hotspots on phones to do that.
Then, all data should be sent directly between devices and not use your data plan. Then, all data should be sent directly between devices and not use your data plan.
<br> <br>
</details> </details>
@@ -224,8 +173,6 @@ Then, all data should be sent directly between devices and not use your data pla
Why don't you implement feature xyz? Why don't you implement feature xyz?
</summary> </summary>
<br>
Snapdrop and PairDrop are a study in radical simplicity. Snapdrop and PairDrop are a study in radical simplicity.
The user interface is insanely simple. The user interface is insanely simple.
Features are chosen very carefully because complexity grows quadratically Features are chosen very carefully because complexity grows quadratically
@@ -237,24 +184,22 @@ Don't be sad. We may decline your feature request for the sake of simplicity.
Read *Insanely Simple: The Obsession that Drives Apple's Success*, Read *Insanely Simple: The Obsession that Drives Apple's Success*,
and/or *Thinking, Fast and Slow* to learn more. and/or *Thinking, Fast and Slow* to learn more.
<br> <br>
</details> </details>
<details> <details>
<summary style="font-size:1.25em;margin-top: 24px; margin-bottom: 16px; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25;"> <summary style="font-size:1.25em;margin-top: 24px; margin-bottom: 16px; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25;">
PairDrop is awesome. How can I support it? Snapdrop and PairDrop are awesome. How can I support them?
</summary> </summary>
<br>
* [Buy me a coffee](https://www.buymeacoffee.com/pairdrop) to pay for the domain and the server, and support libre software. * [Buy me a coffee](https://www.buymeacoffee.com/pairdrop) to pay for the domain and the server, and support libre software.
* [File bugs, give feedback, submit suggestions](https://github.com/schlagmichdoch/pairdrop/issues) * [File bugs, give feedback, submit suggestions](https://github.com/schlagmichdoch/pairdrop/issues)
* Share PairDrop on social media. * Share PairDrop on social media.
* Fix bugs and create a pull request. * Fix bugs and make a pull request.
* Do some security analysis and make suggestions. * Do some security analysis and make suggestions.
* Participate in [active discussions](https://github.com/schlagmichdoch/PairDrop/discussions) * To support the original Snapdrop and its creator go to [his GitHub page](https://github.com/RobinLinus/snapdrop)
<br> <br>
</details> </details>
@@ -264,10 +209,7 @@ and/or *Thinking, Fast and Slow* to learn more.
How does it work? How does it work?
</summary> </summary>
<br>
[See here for info about the technical implementation](/docs/technical-documentation.md) [See here for info about the technical implementation](/docs/technical-documentation.md)
<br> <br>
</details> </details>
+26 -32
View File
@@ -652,74 +652,68 @@ To run PairDrop including its own coturn-server you need to punch holes in the f
<br> <br>
### Firewall
To run PairDrop including its own coturn-server you need to punch holes in the firewall. These ports must be opened additionally:
- 3478 tcp/udp
- 5349 tcp/udp
- 10000:20000 tcp/udp
<br>
## Local Development ## Local Development
### Install ### Install
All files needed for developing are available in the folder `./dev`. All files needed for developing are available on the branch `dev`.
For convenience, there is also a docker compose file for developing: First, [Install docker with docker-compose.](https://docs.docker.com/compose/install/)
#### Developing with docker compose Then, clone the repository and run docker-compose:
First, [Install docker with docker compose.](https://docs.docker.com/compose/install/)
Then, clone the repository and run docker compose:
```bash ```bash
git clone https://github.com/schlagmichdoch/PairDrop.git && cd PairDrop git clone https://github.com/schlagmichdoch/PairDrop.git && cd PairDrop
``` ```
```bash ```bash
docker compose -f docker-compose-dev.yml up --no-deps --build git checkout dev
```
```bash
docker compose -f docker-compose-dev.yml up -d
``` ```
Now point your web browser to `http://localhost:8080`. Now point your web browser to `http://localhost:8080`.
- To restart the containers, run `docker compose restart`.
- To stop the containers, run `docker compose stop`.
- To debug the Node.js server, run `docker logs pairdrop`. - To debug the Node.js server, run `docker logs pairdrop`.
- After changes to the code you have to rerun the `docker compose` command
<br> <br>
#### Testing PWA related features ### Testing PWA related features
PWAs requires the app to be served under a correctly set up and trusted TLS endpoint. PWAs requires the app to be served under a correctly set up and trusted TLS endpoint.
The NGINX container creates a CA certificate and a website certificate for you. The NGINX container creates a CA certificate and a website certificate for you.
To correctly set the common name of the certificate, To correctly set the common name of the certificate,
you need to change the FQDN environment variable in `docker-compose-dev.yml` you need to change the FQDN environment variable in `docker/fqdn.env`
to the fully qualified domain name of your workstation. (Default: localhost) to the fully qualified domain name of your workstation.
If you want to test PWA features, you need to trust the CA of the certificate for your local deployment. \ If you want to test PWA features, you need to trust the CA of the certificate for your local deployment. \
For your convenience, you can download the crt file from `http://<Your FQDN>:8080/ca.crt`. \ For your convenience, you can download the crt file from `http://<Your FQDN>:8080/ca.crt`. \
Install that certificate to the trust store of your operating system. \ Install that certificate to the trust store of your operating system. \
##### Windows - On Windows, make sure to install it to the `Trusted Root Certification Authorities` store.
- Make sure to install it to the `Trusted Root Certification Authorities` store. - On macOS, double-click the installed CA certificate in `Keychain Access`,
##### macOS
- Double-click the installed CA certificate in `Keychain Access`,
- expand `Trust`, and select `Always Trust` for SSL. - expand `Trust`, and select `Always Trust` for SSL.
- Firefox uses its own trust store. To install the CA,
##### Firefox - point Firefox at `http://<Your FQDN>:8080/ca.crt`.
Firefox uses its own trust store. To install the CA:
- point Firefox at `http://<Your FQDN>:8080/ca.crt` (Default: `http://localhost:8080/ca.crt`)
- When prompted, select `Trust this CA to identify websites` and click _OK_. - When prompted, select `Trust this CA to identify websites` and click _OK_.
Alternatively:
1. Download `ca.crt` from `http://<Your FQDN>:8080/ca.crt` (Default: `http://localhost:8080/ca.crt`)
2. Go to `about:preferences#privacy` scroll down to `Security` and `Certificates` and click `View Certificates`
3. Import the downloaded certificate file (step 1)
##### Chrome
- When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`). - When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`).
- Additionally, after installing a new cert, you need to clear the Storage (DevTools → Application → Clear storage → Clear site data). - Additionally, after installing a new cert, you need to clear the Storage (DevTools → Application → Clear storage → Clear site data).
##### Google Chrome
- To skip the installation of the certificate, you can also open `chrome://flags/#unsafely-treat-insecure-origin-as-secure`
- The feature `Insecure origins treated as secure` must be enabled and the list must include your PairDrop test instance. E.g.: `http://127.0.0.1:3000,https://127.0.0.1:8443`
Please note that the certificates (CA and webserver cert) expire after a day. Please note that the certificates (CA and webserver cert) expire after a day.
Also, whenever you restart the NGINX Docker container new certificates are created. Also, whenever you restart the NGINX Docker container new certificates are created.
The site is served on `https://<Your FQDN>:8443` (Default: `https://localhost:8443`). The site is served on `https://<Your FQDN>:8443`.
[< Back](/README.md) [< Back](/README.md)
+2 -2
View File
@@ -45,11 +45,11 @@ This pairdrop-cli version was released alongside v1.10.4
#### Linux / Mac #### Linux / Mac
1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
```shell ```shell
wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.9/pairdrop-cli.zip" wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.6/pairdrop-cli.zip"
``` ```
or or
```shell ```shell
curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.9/pairdrop-cli.zip" curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.6/pairdrop-cli.zip"
``` ```
2. Unzip the archive to a folder of your choice e.g. `/usr/share/pairdrop-cli/` 2. Unzip the archive to a folder of your choice e.g. `/usr/share/pairdrop-cli/`
```shell ```shell
+509 -166
View File
@@ -1,12 +1,12 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.10.9", "version": "1.10.6",
"lockfileVersion": 3, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pairdrop", "name": "pairdrop",
"version": "1.10.9", "version": "1.10.6",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",
@@ -37,12 +37,12 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.5", "content-type": "~1.0.4",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"destroy": "1.2.0", "destroy": "1.2.0",
@@ -50,7 +50,7 @@
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.11.0",
"raw-body": "2.5.2", "raw-body": "2.5.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
@@ -68,18 +68,12 @@
} }
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.7", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": { "dependencies": {
"es-define-property": "^1.0.0", "function-bind": "^1.1.1",
"es-errors": "^1.3.0", "get-intrinsic": "^1.0.2"
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -97,17 +91,17 @@
} }
}, },
"node_modules/content-type": { "node_modules/content-type": {
"version": "1.0.5", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/cookie": { "node_modules/cookie": {
"version": "0.6.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -125,22 +119,6 @@
"ms": "2.0.0" "ms": "2.0.0"
} }
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -171,25 +149,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -204,16 +163,16 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.1",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.5.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@@ -245,9 +204,9 @@
} }
}, },
"node_modules/express-rate-limit": { "node_modules/express-rate-limit": {
"version": "7.3.1", "version": "7.1.5",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.3.1.tgz", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.5.tgz",
"integrity": "sha512-BbaryvkY4wEgDqLgD18/NSy2lDO2jTuT9Y8c1Mpx0X63Yz0sYd5zN6KPe7UvpuSVvV33T6RaE1o1IVZQjHMYgw==", "integrity": "sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
@@ -292,62 +251,32 @@
} }
}, },
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.4", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "function-bind": "^1.1.1",
"function-bind": "^1.1.2", "has": "^1.0.3",
"has-proto": "^1.0.1", "has-symbols": "^1.0.3"
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gopd": { "node_modules/has": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"engines": { "dependencies": {
"node": ">= 0.4" "function-bind": "^1.1.1"
}, },
"funding": { "engines": {
"url": "https://github.com/sponsors/ljharb" "node": ">= 0.4.0"
} }
}, },
"node_modules/has-symbols": { "node_modules/has-symbols": {
@@ -361,17 +290,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -476,12 +394,9 @@
} }
}, },
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.2", "version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"engines": {
"node": ">= 0.4"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@@ -545,9 +460,9 @@
} }
}, },
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.5.2", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
@@ -624,39 +539,19 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
}, },
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.6", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": { "dependencies": {
"call-bind": "^1.0.7", "call-bind": "^1.0.0",
"es-errors": "^1.3.0", "get-intrinsic": "^1.0.2",
"get-intrinsic": "^1.2.4", "object-inspect": "^1.9.0"
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -691,9 +586,9 @@
} }
}, },
"node_modules/ua-parser-js": { "node_modules/ua-parser-js": {
"version": "1.0.38", "version": "1.0.37",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -745,9 +640,9 @@
} }
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.17.1", "version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@@ -764,5 +659,453 @@
} }
} }
} }
},
"dependencies": {
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"requires": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"requires": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
}
},
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"requires": {
"safe-buffer": "5.2.1"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
},
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"express-rate-limit": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.5.tgz",
"integrity": "sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==",
"requires": {}
},
"finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-intrinsic": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
"integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"requires": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
"on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"requires": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"requires": {
"side-channel": "^1.0.4"
}
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"requires": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"requires": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"dependencies": {
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
}
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
"toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"ua-parser-js": {
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ=="
},
"unique-names-generator": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz",
"integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow=="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
},
"ws": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"requires": {}
}
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.10.9", "version": "1.10.6",
"type": "module", "type": "module",
"description": "", "description": "",
"main": "server/index.js", "main": "server/index.js",
+6 -32
View File
@@ -192,21 +192,11 @@
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Arabic)</span> <span>(Arabic)</span>
</button> </button>
<button class="btn fw wrap" value="be">
<span>беларуская</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Belarusian)</span>
</button>
<button class="btn fw wrap" value="ca"> <button class="btn fw wrap" value="ca">
<span>Català</span> <span>Català</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Catalan)</span> <span>(Catalan)</span>
</button> </button>
<button class="btn fw wrap" value="da">
<span>Dansk</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Danish)</span>
</button>
<button class="btn fw wrap" value="de"> <button class="btn fw wrap" value="de">
<span>Deutsch</span> <span>Deutsch</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
@@ -235,21 +225,11 @@
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Italian)</span> <span>(Italian)</span>
</button> </button>
<button class="btn fw wrap" value="he">
<span>עִבְרִית</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Hebrew)</span>
</button>
<button class="btn fw wrap" value="kn"> <button class="btn fw wrap" value="kn">
<span>ಕನ್ನಡ</span> <span>ಕನ್ನಡ</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Kannada)</span> <span>(Kannada)</span>
</button> </button>
<button class="btn fw wrap" value="hu">
<span>Magyar</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Hungarian)</span>
</button>
<button class="btn fw wrap" value="nl"> <button class="btn fw wrap" value="nl">
<span>Nederlands</span> <span>Nederlands</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
@@ -260,11 +240,6 @@
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Norwegian)</span> <span>(Norwegian)</span>
</button> </button>
<button class="btn fw wrap" value="pl">
<span>Polski</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Polish)</span>
</button>
<button class="btn fw wrap" value="pt-BR"> <button class="btn fw wrap" value="pt-BR">
<span>Português do Brasil</span> <span>Português do Brasil</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
@@ -288,12 +263,7 @@
<button class="btn fw wrap" value="zh-CN"> <button class="btn fw wrap" value="zh-CN">
<span>中文</span> <span>中文</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Simplified Chinese)</span> <span>(Chinese)</span>
</button>
<button class="btn fw wrap" value="zh-TW">
<span>漢語</span>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<span>(Traditional Chinese)</span>
</button> </button>
<button class="btn fw wrap" value="ja"> <button class="btn fw wrap" value="ja">
<span>日本語</span> <span>日本語</span>
@@ -612,7 +582,7 @@
</svg> </svg>
<div class="title-wrapper" dir="ltr"> <div class="title-wrapper" dir="ltr">
<h1>PairDrop</h1> <h1>PairDrop</h1>
<div class="font-subheading">v1.10.9</div> <div class="font-subheading">v1.10.6</div>
</div> </div>
<div class="font-subheading" data-i18n-key="about.claim" data-i18n-attrs="text"></div> <div class="font-subheading" data-i18n-key="about.claim" data-i18n-attrs="text"></div>
<div class="row"> <div class="row">
@@ -767,6 +737,10 @@
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--> <!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
<path d="M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8V444.8C394 378 431.1 230.1 432 141.4L256 66.8l0 0z"></path> <path d="M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8V444.8C394 378 431.1 230.1 432 141.4L256 66.8l0 0z"></path>
</symbol> </symbol>
<symbol id="turn-indicator" viewBox="0 0 576 512">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M0 80C0 53.5 21.5 32 48 32h96c26.5 0 48 21.5 48 48V96H384V80c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H432c-26.5 0-48-21.5-48-48V160H192v16c0 1.7-.1 3.4-.3 5L272 288h96c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H272c-26.5 0-48-21.5-48-48V336c0-1.7 .1-3.4 .3-5L144 224H48c-26.5 0-48-21.5-48-48V80z"></path>
</symbol>
</svg> </svg>
<!-- Scripts --> <!-- Scripts -->
<script src="scripts/localization.js" defer></script> <script src="scripts/localization.js" defer></script>
+2 -2
View File
@@ -70,9 +70,9 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة", "x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة",
"x-instructions-share-mode_desktop": "انقر للإرسال {{descriptor}}", "x-instructions-share-mode_desktop": "انقر للإرسال",
"activate-share-mode-and-other-files-plural": "و{{count}} ملفات أخرى", "activate-share-mode-and-other-files-plural": "و{{count}} ملفات أخرى",
"x-instructions-share-mode_mobile": "انقر للإرسال {{descriptor}}", "x-instructions-share-mode_mobile": "انقر للإرسال",
"activate-share-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال", "activate-share-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال",
"no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من أن تكتشف على الشبكات الأخرى", "no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من أن تكتشف على الشبكات الأخرى",
"activate-share-mode-shared-text": "النص المشترك", "activate-share-mode-shared-text": "النص المشترك",
-184
View File
@@ -1,184 +0,0 @@
{
"header": {
"about_aria-label": "Адкрыйце Аб PairDrop",
"about_title": "Аб PairDrop",
"theme-auto_title": "Аўтаматычная адаптацыя тэмы да сістэмы",
"theme-light_title": "Заўсёды выкарыстоўваць светлую тэму",
"theme-dark_title": "Заўсёды выкарыстоўваць цёмную тэму",
"notification_title": "Уключыць апавяшчэнні",
"edit-paired-devices_title": "Рэдагаваць злучаныя прылады",
"join-public-room_title": "Часова далучыцца да публічнага пакоя",
"cancel-share-mode": "Адмяніць",
"language-selector_title": "Задаць мову",
"install_title": "Усталяваць PairDrop",
"pair-device_title": "Злучыце свае прылады назаўжды",
"edit-share-mode": "Рэдагаваць",
"expand_title": "Разгарнуць радок кнопак"
},
"instructions": {
"no-peers_data-drop-bg": "Адпусціце, каб выбраць атрымальніка",
"no-peers-title": "Адкрыйце PairDrop на іншых прыладах, каб адправіць файлы",
"x-instructions_data-drop-peer": "Адпусціце, каб адправіць вузлу",
"x-instructions_data-drop-bg": "Адпусціце, каб выбраць атрымальніка",
"x-instructions-share-mode_mobile": "Дакраніцеся, каб адправіць {{descriptor}}",
"activate-share-mode-and-other-file": "і 1 іньшы файл",
"activate-share-mode-and-other-files-plural": "і {{count}} іньшых файла(ў)",
"activate-share-mode-shared-text": "агульны тэкст",
"activate-share-mode-shared-files-plural": "{{count}} агульных файлаў",
"webrtc-requirement": "Каб выкарыстоўваць гэты асобнік Pair Drop, WebRTC павінен быць уключаны!",
"no-peers-subtitle": "Злучыце прылады або ўвайдзіце ў публічны пакой, каб вас маглі выявіць з іншых сетак",
"x-instructions_mobile": "Дакраніцеся, каб адправіць файлы, або доўга трымайце, каб адправіць паведамленне",
"x-instructions-share-mode_desktop": "Націсніце, каб адправіць {{descriptor}}",
"x-instructions_desktop": "Націсніце, каб адправіць файлы, або націсніце правай кнопкай мышы, каб адправіць паведамленне",
"activate-share-mode-base": "Адкрыйце PairDrop на іншых прыладах, каб адправіць",
"activate-share-mode-shared-file": "агульны файл"
},
"footer": {
"known-as": "Вы вядомыя як:",
"display-name_data-placeholder": "Загрузка…",
"discovery": "Вас могуць выявіць:",
"on-this-network_title": "Вас можа знайсці кожны ў гэтай сетцы.",
"paired-devices": "з дапамогай злучаных прылад",
"public-room-devices_title": "Вас могуць выявіць прылады ў гэтай публічнай пакоі незалежна ад сеткі.",
"traffic": "Рух",
"display-name_title": "Зменіце назву сваёй прылады назаўжды",
"on-this-network": "у гэтай сетцы",
"paired-devices_title": "Злучаныя прылады заўсёды могуць вас выявіць незалежна ад сеткі.",
"public-room-devices": "у пакоі {{roomId}}",
"webrtc": ", калі WebRTC недаступны.",
"routed": "накіроўваецца праз сервер"
},
"dialogs": {
"hr-or": "АБО",
"cancel": "Адмяніць",
"pair": "Злучыць",
"unpair": "Разлучыць",
"paired-devices-wrapper_data-empty": "Няма злучаных прылад.",
"auto-accept": "аўтаматычнае прыняцце",
"close": "Закрыць",
"join": "Далучыцца",
"leave": "Пакінуць",
"decline": "Адмовіць",
"share": "Падзяліцца",
"copy": "Капіяваць",
"title-image": "Малюнак",
"system-language": "Мова сістэмы",
"public-room-qr-code_title": "Націсніце, каб скапіяваць спасылку на публічны пакой",
"title-file": "Файл",
"title-file-plural": "Файлы",
"message_placeholder": "Тэкст",
"language-selector-title": "Задаць мову",
"send-message-title": "Адправіць паведамленне",
"scan-qr-code": "або сканаваць QR-код.",
"enter-room-id-from-another-device": "Увядзіце ID пакоя з іншай прылады, каб далучыцца да пакоя.",
"edit-paired-devices-title": "Рэдагаваць злучаныя прылады",
"auto-accept-instructions-1": "Актываваць",
"auto-accept-instructions-2": ", каб аўтаматычна прымаць усе файлы, адпраўленыя з гэтай прылады.",
"accept": "Прыняць",
"download": "Спампаваць",
"download-again": "Спампаваць яшчэ раз",
"pair-devices-qr-code_title": "Націсніце, каб скапіраваць спасылку для спалучэння гэтай прылады",
"approve": "сцвердзіць",
"pair-devices-title": "Пастаяннае злучэнне прылад",
"enter-key-from-another-device": "Увядзіце тут ключ з іншай прылады.",
"temporary-public-room-title": "Часовы публічны пакой",
"input-room-id-on-another-device": "Увядзіце гэты ID пакоя на іншай прыладзе",
"paired-device-removed": "Злучаная прылада была выдалена.",
"would-like-to-share": "хацеў бы падзяліцца",
"send-message-to": "Каму:",
"message_title": "Устаўце паведамленне для адпраўкі",
"has-sent": "адправіў:",
"base64-processing": "Апрацоўка…",
"send": "Адправіць",
"base64-title-text": "Падзяліцца тэкстам",
"base64-title-files": "Падзяліцца файламі",
"base64-tap-to-paste": "Дакраніцеся тут, каб падзяліцца {{type}}",
"file-other-description-image": "і 1 іньшы малюнак",
"file-other-description-image-plural": "і {{count}} іншых малюнкаў",
"file-other-description-file-plural": "і {{count}} іншых файлаў",
"title-image-plural": "Малюнкі",
"share-text-subtitle": "Рэдагаваць паведамленне перад адпраўкай:",
"share-text-title": "Падзяліцца тэкставым паведамленнем",
"close-toast_title": "Закрыць апавяшчэнне",
"receive-text-title": "Паведамленне атрымана",
"input-key-on-this-device": "Увядзіце гэты ключ на іншай прыладзе",
"base64-files": "файлы",
"base64-text": "тэкст",
"base64-paste-to-send": "Устаўце сюды буфер абмену, каб падзяліцца {{type}}",
"file-other-description-file": "і 1 іньшы файл",
"receive-title": "{{descriptor}} атрымана",
"share-text-checkbox": "Заўсёды паказваць гэта дыялогавае акно пры абагульванні тэксту"
},
"about": {
"buy-me-a-coffee_title": "Купіць мне кавы!",
"mastodon_title": "Напішыце пра PairDrop на Mastodon",
"tweet_title": "Твіт пра PairDrop",
"github_title": "PairDrop на GitHub",
"custom_title": "Сачыце за намі",
"bluesky_title": "Сачыце за намі на BlueSky",
"faq_title": "Часта задаюць пытанні",
"close-about_aria-label": "Закрыць Аб PairDrop",
"claim": "Самы просты спосаб перадачы файлаў паміж прыладамі",
"privacypolicy_title": "Адкрыйце нашу палітыку прыватнасці"
},
"notifications": {
"link-received": "Спасылка атрымана {{name}} - Націсніце, каб адкрыць",
"message-received": "Паведамленне атрымана {{name}} - Націсніце, каб скапіяваць",
"click-to-download": "Націсніце, каб спампаваць",
"click-to-show": "Націсніце, каб паказаць",
"copied-text-error": "Памылка запісу ў буфер абмену. Скапіруйце ўручную!",
"online": "Вы зноў у сетцы",
"online-requirement-public-room": "Вы павінны быць падлучаныя да сеткі, каб стварыць агульны пакой",
"connecting": "Падключэнне…",
"public-room-id-invalid": "Несапраўдны ID пакоя",
"notifications-permissions-error": "Дазвол на апавяшчэнні быў заблакіраваны, бо карыстальнік некалькі разоў адхіляў запыт на дазвол. Гэта можна скінуць у меню \"Аб старонцы\", доступ да якой можна атрымаць, націснуўшы на значок замка побач з радком URL.",
"request-title": "{{name}} хоча перадаць {{count}} {{descriptor}}",
"copied-text": "Тэкст скапіраваны ў буфер абмену",
"offline": "Вы па-за сеткай",
"connected": "Падключана",
"online-requirement-pairing": "Вы павінны быць падлучаныя да сеткі для спалучэння прылад",
"pairing-key-invalidated": "Ключ {{key}} несапраўдны",
"display-name-random-again": "Адлюстраванае імя зноў згенеравалася выпадковым чынам",
"download-successful": "{{descriptor}} спампавана",
"pairing-tabs-error": "Злучэнне дзвюх укладак вэб-браўзера немагчыма",
"display-name-changed-permanently": "Адлюстроўванае імя было зменена назаўжды",
"pairing-cleared": "Усе прылады раз'яднаны",
"room-url-copied-to-clipboard": "Спасылка на публічны пакой скапіравана ў буфер абмену",
"public-room-left": "Пакінуць публічны пакой {{publicRoomId}}",
"text-content-incorrect": "Змест тэксту няправільны",
"clipboard-content-incorrect": "Змест буфера абмену няправільны",
"notifications-enabled": "Апавяшчэнні ўключаны",
"files-incorrect": "Няправільныя файлы",
"file-transfer-completed": "Перадача файла завершана",
"selected-peer-left": "Выбраны вузел выйшаў",
"copied-to-clipboard": "Скапіравана ў буфер абмену",
"pair-url-copied-to-clipboard": "Спасылка для злучэння гэтай прылады скапіравана ў буфер абмену",
"pairing-success": "Злучаныя прылады",
"copied-to-clipboard-error": "Капіраванне немагчымае. Скапіруйце ўручную.",
"file-content-incorrect": "Змест файла няправільны",
"pairing-not-persistent": "Злучаныя прылады не з'яўляюцца пастаяннымі",
"pairing-key-invalid": "Несапраўдны ключ",
"display-name-changed-temporarily": "Адлюстраванае імя зменена толькі для гэтага сеансу",
"ios-memory-limit": "Адначасовая адпраўка файлаў на iOS магчымая толькі да 200 МБ",
"message-transfer-completed": "Перадача паведамлення завершана",
"unfinished-transfers-warning": "Ёсць незавершаныя перадачы. Вы ўпэўнены, што хочаце закрыць PairDrop?",
"rate-limit-join-key": "Ліміт хуткасці дасягнуты. Пачакайце 10 секунд і паўтарыце спробу."
},
"peer-ui": {
"preparing": "Падрыхтоўка…",
"waiting": "Чаканне…",
"transferring": "Перадача…",
"processing": "Апрацоўка…",
"click-to-send-share-mode": "Націсніце, каб адправіць {{descriptor}}",
"connection-hash": "Каб праверыць бяспеку скразнога шыфравання, параўнайце гэты нумар бяспекі на абедзвюх прыладах",
"click-to-send": "Націсніце, каб адправіць файлы, або націсніце правай кнопкай мышы, каб адправіць паведамленне"
},
"document-titles": {
"file-received": "Файл атрыманы",
"image-transfer-requested": "Запытана перадача малюнкаў",
"message-received": "Паведамленне атрымана",
"message-received-plural": "Атрымана {{count}} паведамленняў",
"file-received-plural": "Атрымана {{count}} файлаў",
"file-transfer-requested": "Запытана перадача файла"
}
}
+2 -7
View File
@@ -30,8 +30,7 @@
"cancel-share-mode": "Cancel·lar", "cancel-share-mode": "Cancel·lar",
"about_title": "Sobre PairDrop", "about_title": "Sobre PairDrop",
"about_aria-label": "Obre Sobre PairDrop", "about_aria-label": "Obre Sobre PairDrop",
"theme-light_title": "Utilitza sempre el mode clar", "theme-light_title": "Utilitza sempre el mode clar"
"expand_title": "Expandeix la fila de botons de la capçalera"
}, },
"dialogs": { "dialogs": {
"message_placeholder": "Text", "message_placeholder": "Text",
@@ -166,11 +165,7 @@
"close-about_aria-label": "Tanca Sobre PairDrop", "close-about_aria-label": "Tanca Sobre PairDrop",
"buy-me-a-coffee_title": "Convida'm a un cafè!", "buy-me-a-coffee_title": "Convida'm a un cafè!",
"github_title": "PairDrop a GitHub", "github_title": "PairDrop a GitHub",
"faq_title": "Preguntes freqüents", "faq_title": "Preguntes freqüents"
"mastodon_title": "Escriu sobre PairDrop a Mastodon",
"bluesky_title": "Segueix-nos a BlueSky",
"custom_title": "Segueix-nos",
"privacypolicy_title": "Obre la nostra política de privacitat"
}, },
"document-titles": { "document-titles": {
"file-transfer-requested": "Transferència de Fitxers Sol·licitada", "file-transfer-requested": "Transferència de Fitxers Sol·licitada",
-9
View File
@@ -1,9 +0,0 @@
{
"header": {
"about_aria-label": "Otevřít o PairDrop",
"about_title": "O službě PairDrop",
"language-selector_title": "Nastavit jazyk",
"theme-auto_title": "Automatické přizpůsobení tématu systému",
"pair-device_title": "Spárovat zařízení permanentně"
}
}
-184
View File
@@ -1,184 +0,0 @@
{
"notifications": {
"public-room-left": "Forlod det offentlige rum {{publicRoomId}}",
"room-url-copied-to-clipboard": "Link til offentligt rum kopieret til udklipsholder",
"notifications-enabled": "Notifikationer aktiveret",
"notifications-permissions-error": "Notifikationstilladelsen er blevet blokeret, da brugeren har afvist tilladelsesprompten flere gange. Dette kan nulstilles i sideoplysninger, som du kan få adgang til ved at klikke på låseikonet ved siden af URL-linjen.",
"copied-text-error": "Skrivning til udklipsholder mislykkedes. Kopier manuelt!",
"ios-memory-limit": "Det er kun muligt at sende filer til iOS op til 200 MB på én gang",
"display-name-random-again": "Vist navn genereres tilfældigt igen",
"display-name-changed-permanently": "Det viste navn blec ændret permanent",
"display-name-changed-temporarily": "Vist navn blev kun ændret for denne session",
"download-successful": "{{descriptor}} hentet",
"pairing-tabs-error": "Det er umuligt at parre to webbrowserfaner",
"pairing-success": "Enheder parret",
"pairing-not-persistent": "Parrede enheder er ikke vedvarende",
"pairing-key-invalid": "Ugyldig nøgle",
"pairing-key-invalidated": "Nøglen {{key}} er ugyldig",
"pairing-cleared": "Alle enheder er frakoblet",
"public-room-id-invalid": "Ugyldigt rum-id",
"copied-to-clipboard": "Kopieret til udklipsholder",
"pair-url-copied-to-clipboard": "Link til at parre denne enhed kopieret til udklipsholder",
"copied-to-clipboard-error": "Kopiering ikke mulig. Kopier manuelt.",
"text-content-incorrect": "Tekstindholdet er forkert",
"file-content-incorrect": "Filens indhold er forkert",
"clipboard-content-incorrect": "Udklipsholderens indhold er forkert",
"link-received": "Link modtaget af {{name}} - Klik for at åbne",
"message-received": "Besked modtaget af {{name}} - Klik for at kopiere",
"click-to-download": "Klik for at hente",
"request-title": "{{name}} vil gerne overføre {{count}} {{descriptor}}",
"click-to-show": "Klik for at vise",
"copied-text": "Kopieret tekst til udklipsholder",
"offline": "Du er offline",
"online": "Du er online igen",
"connected": "Forbundet",
"online-requirement-pairing": "Du skal være online for at parre enheder",
"online-requirement-public-room": "Du skal være online for at oprette et offentligt rum",
"connecting": "Forbinder…",
"files-incorrect": "Filerne er forkerte",
"file-transfer-completed": "Filoverførsel gennemført",
"message-transfer-completed": "Beskedoverførsel gennemført",
"unfinished-transfers-warning": "Der er uafsluttede overførsler. Er du sikker på, at du vil lukke PairDrop?",
"rate-limit-join-key": "Satsgrænsen er nået. Vent 10 sekunder, og prøv igen.",
"selected-peer-left": "Valgt peer forlod"
},
"dialogs": {
"message_placeholder": "Tekst",
"base64-files": "filer",
"file-other-description-image": "og 1 andet billede",
"file-other-description-file": "og 1 anden fil",
"download-again": "Hent igen",
"system-language": "Systemsprog",
"pair-devices-qr-code_title": "Klik for at kopiere linket for at parre denne enhed",
"enter-key-from-another-device": "Indtast nøgle fra en anden enhed her.",
"temporary-public-room-title": "Midlertidigt offentligt rum",
"edit-paired-devices-title": "Rediger parrede enheder",
"auto-accept-instructions-2": "for automatisk at acceptere alle filer sendt fra den pågældende enhed.",
"pair-devices-title": "Par enheder permanent",
"input-key-on-this-device": "Indtast denne nøgle på en anden enhed",
"scan-qr-code": "eller scan QR-koden.",
"input-room-id-on-another-device": "Indtast dette rum-id på en anden enhed",
"enter-room-id-from-another-device": "Indtast rum-id fra en anden enhed for at deltage i rummet.",
"hr-or": "ELLER",
"pair": "Par",
"cancel": "Annuller",
"unpair": "Fjern parring",
"paired-device-removed": "Parret enhed er blevet fjernet.",
"paired-devices-wrapper_data-empty": "Ingen parrede enheder.",
"auto-accept-instructions-1": "Aktiver",
"auto-accept": "auto-accepter",
"close": "Luk",
"join": "Forbinde",
"leave": "Forlad",
"would-like-to-share": "gerne vil dele",
"accept": "Accepter",
"decline": "Nægt",
"has-sent": "har sendt:",
"share": "Del",
"download": "Hent",
"send-message-title": "Send besked",
"send-message-to": "Til:",
"message_title": "Indsæt besked for at sende",
"send": "Send",
"receive-text-title": "Besked modtaget",
"copy": "Kopier",
"base64-title-files": "Del filer",
"base64-title-text": "Del tekst",
"base64-processing": "Behandler…",
"base64-tap-to-paste": "Tryk her for at dele {{type}}",
"base64-paste-to-send": "Indsæt udklipsholder her for at dele {{type}}",
"base64-text": "tekst",
"file-other-description-image-plural": "og {{count}} andre billeder",
"file-other-description-file-plural": "og {{count}} andre filer",
"title-image": "Billede",
"title-file": "Fil",
"title-image-plural": "Billeder",
"title-file-plural": "Filer",
"receive-title": "{{descriptor}} Modtaget",
"language-selector-title": "Indstil sprog",
"public-room-qr-code_title": "Klik for at kopiere linket til det offentlige rum",
"approve": "godkend",
"share-text-title": "Del tekstbesked",
"share-text-subtitle": "Rediger besked, før du sender:",
"share-text-checkbox": "Vis altid denne dialogboks, når du deler tekst",
"close-toast_title": "Luk besked"
},
"about": {
"claim": "Den nemmeste måde at overføre filer på tværs af enheder",
"faq_title": "Ofte stillede spørgsmål",
"close-about_aria-label": "Luk Om PairDrop",
"github_title": "PairDrop på GitHub",
"buy-me-a-coffee_title": "Køb mig en kop kaffe!",
"tweet_title": "Tweet om PairDrop",
"mastodon_title": "Skriv om PairDrop på Mastodon",
"bluesky_title": "Følg os på BlueSky",
"custom_title": "Følg os",
"privacypolicy_title": "Åbn vores privatlivspolitik"
},
"header": {
"language-selector_title": "Indstil sprog",
"about_aria-label": "Åbn Om PairDrop",
"theme-auto_title": "Tilpas temaet til systemet automatisk",
"theme-light_title": "Brug altid lyst tema",
"theme-dark_title": "Brug altid mørkt tema",
"notification_title": "Aktiver notifikationer",
"install_title": "Installer PairDrop",
"pair-device_title": "Par dine enheder permanent",
"edit-paired-devices_title": "Rediger parrede enheder",
"join-public-room_title": "Deltag midlertidigt i det offentlige rum",
"cancel-share-mode": "Annuller",
"edit-share-mode": "Redigere",
"expand_title": "Udvid overskriftsknaprækken",
"about_title": "Om PairDrop"
},
"instructions": {
"no-peers-subtitle": "Par enheder, eller gå ind i et offentligt rum for at være synlig på andre netværk",
"x-instructions_desktop": "Klik for at sende filer eller højreklik for at sende en besked",
"activate-share-mode-base": "Åbn PairDrop på andre enheder for at sende",
"no-peers_data-drop-bg": "Slip for at vælge modtager",
"no-peers-title": "Åbn PairDrop på andre enheder for at sende filer",
"x-instructions_mobile": "Tryk for at sende filer, eller tryk længe for at sende en besked",
"x-instructions_data-drop-peer": "Slip for at sende til peer",
"x-instructions_data-drop-bg": "Slip for at vælge modtager",
"x-instructions-share-mode_desktop": "Klik for at sende {{descriptor}}",
"x-instructions-share-mode_mobile": "Tryk for at sende {{descriptor}}",
"activate-share-mode-and-other-file": "og 1 anden fil",
"activate-share-mode-and-other-files-plural": "og {{count}} andre filer",
"activate-share-mode-shared-text": "delt tekst",
"activate-share-mode-shared-file": "delt fil",
"activate-share-mode-shared-files-plural": "{{count}} delte filer",
"webrtc-requirement": "For at bruge denne PairDrop-instans skal WebRTC være aktiveret!"
},
"footer": {
"on-this-network_title": "Du kan blive opdaget af alle på dette netværk.",
"public-room-devices_title": "Du kan blive opdaget af enheder i dette offentlige rum uafhængigt af netværket.",
"known-as": "Du er kendt som:",
"display-name_data-placeholder": "Indlæser…",
"display-name_title": "Rediger dit enhedsnavn permanent",
"discovery": "Du kan blive opdaget:",
"on-this-network": "på dette netværk",
"paired-devices": "af parrede enheder",
"paired-devices_title": "Du kan til enhver tid blive opdaget af parrede enheder uafhængigt af netværket.",
"public-room-devices": "i rum {{roomId}}",
"traffic": "Trafikken er",
"routed": "dirigeret gennem serveren",
"webrtc": "hvis WebRTC ikke er tilgængelig."
},
"document-titles": {
"file-received": "Fil modtaget",
"file-received-plural": "{{count}} Filer modtaget",
"file-transfer-requested": "Filoverførsel anmodet",
"image-transfer-requested": "Billedoverførsel anmodet",
"message-received": "Besked modtaget",
"message-received-plural": "{{count}} meddelelser modtaget"
},
"peer-ui": {
"click-to-send-share-mode": "Klik for at sende {{descriptor}}",
"click-to-send": "Klik for at sende filer eller højreklik for at sende en besked",
"connection-hash": "For at kontrollere sikkerheden for end-to-end-kryptering skal du sammenligne dette sikkerhedsnummer på begge enheder",
"preparing": "Forbereder…",
"waiting": "Venter…",
"processing": "Behandler…",
"transferring": "Overfører…"
}
}
+1
View File
@@ -178,6 +178,7 @@
"click-to-send-share-mode": "Click to send {{descriptor}}", "click-to-send-share-mode": "Click to send {{descriptor}}",
"click-to-send": "Click to send files or right click to send a message", "click-to-send": "Click to send files or right click to send a message",
"connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
"turn-indicator": "Transfer over the Internet",
"connecting": "Connecting…", "connecting": "Connecting…",
"preparing": "Preparing…", "preparing": "Preparing…",
"waiting": "Waiting…", "waiting": "Waiting…",
-1
View File
@@ -1 +0,0 @@
{}
+9 -28
View File
@@ -11,9 +11,7 @@
"pair-device_title": "Associez vos appareils de manière permanente", "pair-device_title": "Associez vos appareils de manière permanente",
"edit-paired-devices_title": "Gérer les appareils couplés", "edit-paired-devices_title": "Gérer les appareils couplés",
"join-public-room_title": "Rejoindre temporairement la salle publique", "join-public-room_title": "Rejoindre temporairement la salle publique",
"cancel-share-mode": "Terminé", "cancel-share-mode": "Terminé"
"edit-share-mode": "Modifier",
"expand_title": "Agrandir entête bouton ligne"
}, },
"instructions": { "instructions": {
"no-peers_data-drop-bg": "Déposer pour choisir le destinataire", "no-peers_data-drop-bg": "Déposer pour choisir le destinataire",
@@ -23,15 +21,11 @@
"x-instructions_mobile": "Appuyez pour envoyer des fichiers ou appuyez longuement pour envoyer un message", "x-instructions_mobile": "Appuyez pour envoyer des fichiers ou appuyez longuement pour envoyer un message",
"x-instructions_data-drop-peer": "Déposer pour envoyer au destinataire", "x-instructions_data-drop-peer": "Déposer pour envoyer au destinataire",
"x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire", "x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire",
"x-instructions-share-mode_desktop": "Cliquez pour envoyer {{descriptor}}", "x-instructions-share-mode_desktop": "Cliquez pour envoyer",
"x-instructions-share-mode_mobile": "Appuyez pour envoyer {{descriptor}}", "x-instructions-share-mode_mobile": "Appuyez pour envoyer",
"activate-share-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer", "activate-share-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer",
"activate-share-mode-and-other-files-plural": "et {{count}} autres fichiers", "activate-share-mode-and-other-files-plural": "et {{count}} autres fichiers",
"activate-share-mode-shared-text": "texte partagé", "activate-share-mode-shared-text": "texte partagé"
"activate-share-mode-shared-file": "fichier partagé",
"webrtc-requirement": "Pour utiliser cette instance de PairDrop, WebRTC doit être activé !",
"activate-share-mode-shared-files-plural": "{{count}} fichiers partagés",
"activate-share-mode-and-other-file": "et un autre fichier"
}, },
"footer": { "footer": {
"known-as": "Vous êtes connu comme :", "known-as": "Vous êtes connu comme :",
@@ -76,13 +70,13 @@
"share": "Partage", "share": "Partage",
"download": "Télécharger", "download": "Télécharger",
"send-message-title": "Envoyer un message", "send-message-title": "Envoyer un message",
"send-message-to": "À :", "send-message-to": "Envoyer un message à",
"send": "Envoyer", "send": "Envoyer",
"receive-text-title": "Message reçu", "receive-text-title": "Message reçu",
"copy": "Copier", "copy": "Copier",
"base64-processing": "Traitement…", "base64-processing": "Traitement…",
"base64-tap-to-paste": "Appuyez ici pour partager {{type}}", "base64-tap-to-paste": "Appuyez ici pour coller {{type}}",
"base64-paste-to-send": "Coller le presse-papiers ici pour partager {{type}}", "base64-paste-to-send": "Coller ici pour envoyer {{type}}",
"base64-text": "texte", "base64-text": "texte",
"base64-files": "fichiers", "base64-files": "fichiers",
"file-other-description-image": "et 1 autre image", "file-other-description-image": "et 1 autre image",
@@ -99,16 +93,7 @@
"system-language": "Langue du système", "system-language": "Langue du système",
"message_title": "Insérer un message à envoyer", "message_title": "Insérer un message à envoyer",
"pair-devices-qr-code_title": "Cliquer pour copier pour appairer l'appareil", "pair-devices-qr-code_title": "Cliquer pour copier pour appairer l'appareil",
"public-room-qr-code_title": "Cliquez pour copier le lien vers le salon public", "public-room-qr-code_title": "Cliquez pour copier le lien vers le salon public"
"base64-title-text": "Texte partagé",
"paired-device-removed": "L'appareil connecté a été enlevé.",
"message_placeholder": "Texte",
"base64-title-files": "Fichiers partagés",
"approve": "approuve",
"share-text-title": "Partage le message",
"share-text-subtitle": "Modifier le message avant l'envoi:",
"share-text-checkbox": "Toujours montrer ce dialogue quand du texte est partagé",
"close-toast_title": "Fermer la notification"
}, },
"about": { "about": {
"close-about_aria-label": "Fermer à propos de PairDrop", "close-about_aria-label": "Fermer à propos de PairDrop",
@@ -116,11 +101,7 @@
"github_title": "PairDrop sur GitHub", "github_title": "PairDrop sur GitHub",
"buy-me-a-coffee_title": "Achetez-moi un café !", "buy-me-a-coffee_title": "Achetez-moi un café !",
"tweet_title": "Tweet à propos de PairDrop", "tweet_title": "Tweet à propos de PairDrop",
"faq_title": "Questions fréquemment posées", "faq_title": "Questions fréquemment posées"
"bluesky_title": "Suis-nous sur BlueSky",
"custom_title": "Suis-nous",
"privacypolicy_title": "Ouvert sur notre politique de confidentialité",
"mastodon_title": "Écrire à propos de PairDrop sur Mastodon"
}, },
"notifications": { "notifications": {
"display-name-changed-permanently": "Le nom d'affichage est modifié de manière permanente", "display-name-changed-permanently": "Le nom d'affichage est modifié de manière permanente",
-184
View File
@@ -1,184 +0,0 @@
{
"header": {
"about_title": "אודות PairDrop",
"theme-light_title": "השתמש תמיד במצב בהיר",
"install_title": "התקן את PairDrop",
"edit-share-mode": "עריכה",
"expand_title": "הרחב את שורת כפתור הכותרת",
"language-selector_title": "שינוי השפה",
"about_aria-label": "פתח אודות PairDrop",
"theme-auto_title": "התאם את הרקע למערכת באופן אוטומטי",
"theme-dark_title": "השתמש תמיד במצב כהה",
"notification_title": "הפעל התראות",
"pair-device_title": "התאם את המכשירים שלך לתמיד",
"edit-paired-devices_title": "עריכת מכשירים מתואמים",
"join-public-room_title": "הצטרף לחדר ציבורי באופן זמני",
"cancel-share-mode": "ביטול"
},
"instructions": {
"no-peers-subtitle": "תאם מכשירים או היכנס לחדר ציבורי כדי להיות ניתן לגילוי ברשתות אחרות",
"x-instructions_data-drop-bg": "שחרר כדי לבחור נמען",
"activate-share-mode-and-other-file": "וקובץ 1 אחר",
"activate-share-mode-base": "פתח את PairDrop על מכשירים אחרים כדי לשלוח",
"activate-share-mode-shared-file": "קובץ משותף",
"activate-share-mode-shared-files-plural": "{{count}} קבצים משותפים",
"webrtc-requirement": "כדי להשתמש בPairdrop, WebRTC מוכרח להיות מופעל!",
"no-peers_data-drop-bg": "שחרר כדי לבחור את הנמען",
"no-peers-title": "פתח את PairDrop במכשירים אחרים כדי לשלוח קבצים",
"x-instructions_desktop": "לחץ כדי לשלוח קבצים או בצע לחיצה ימנית כדי לשלוח הודעה",
"x-instructions_mobile": "גע כדי לשלוח קבצים או בצע נגיעה ארוכה כדי לשלוח הודעה",
"x-instructions_data-drop-peer": "שחרר כדי לשלוח למכשיר",
"x-instructions-share-mode_desktop": "לחץ כדי לשלוח {{descriptor}}",
"x-instructions-share-mode_mobile": "גע כדי לשלוח {{descriptor}}",
"activate-share-mode-and-other-files-plural": "ו{{count}} קבצים אחרים",
"activate-share-mode-shared-text": "טקסט משותף"
},
"footer": {
"paired-devices_title": "הנך ניתן לגילוי על ידי מכשירים מתואמים בכל עת ללא תלות ברשת.",
"on-this-network": "ברשת הזו",
"on-this-network_title": "אתה ניתן לגילוי על ידי כולם ברשת הזו.",
"display-name_data-placeholder": "טוען…",
"display-name_title": "שנה את שם המכשיר שלך לתמיד",
"known-as": "הנך ידוע כ:",
"discovery": "הנך ניתן לגילוי:",
"paired-devices": "על ידי מכשירים מתואמים",
"public-room-devices": "בחדר {{roomId}}",
"public-room-devices_title": "הנך ניתן לגילוי על ידי מכשירים בחדר הציבורי הזה ללא תלות ברשת.",
"traffic": "הנתונים",
"routed": "מנותבים דרך השרת",
"webrtc": "אם WebRTC אינו זמין."
},
"dialogs": {
"input-room-id-on-another-device": "הזן את מזהה החדר הזה במכשיר אחר",
"edit-paired-devices-title": "ערוך מכשירים מתואמים",
"paired-device-removed": "המכשיר המתואם הוסר.",
"download-again": "הורד שוב",
"public-room-qr-code_title": "לחץ כדי להעתיק את הקישור לחדר הציבורי",
"auto-accept-instructions-2": "כדי לקבל באופן אוטומטי את כל הקבצים שנשלחים ממכשיר זה.",
"title-file-plural": "קבצים",
"receive-title": "{{descriptor}} התקבל",
"download": "הורד",
"send-message-title": "שלח הודעה",
"message_placeholder": "טקסט",
"receive-text-title": "ההודעה התקבלה",
"base64-text": "טקסט",
"share-text-checkbox": "תמיד הצג את חלונית זו כאשר טקסט משותף",
"system-language": "שפת המערכת",
"title-file": "קובץ",
"pair-devices-title": "תאם מכשירים לתמיד",
"input-key-on-this-device": "הזן את הקוד הזה במכשיר אחר",
"scan-qr-code": "או סרוק את הברקוד.",
"enter-key-from-another-device": "הזן את הקוד ממכשיר אחר כאן.",
"temporary-public-room-title": "חדר ציבורי זמני",
"enter-room-id-from-another-device": "הזן מזהה חדר ממכשיר אחר כדי להצטרף לחדר.",
"hr-or": "או",
"pair": "תאם",
"cancel": "ביטול",
"unpair": "בטל התאמה",
"paired-devices-wrapper_data-empty": "אין מכשירים מתואמים.",
"auto-accept-instructions-1": "הפעל",
"auto-accept": "קבלה אוטומטית",
"close": "סגירה",
"join": "הצטרף",
"leave": "עזוב",
"would-like-to-share": "רוצה לשתף",
"accept": "קבל",
"decline": "סרב",
"has-sent": "שלח:",
"share": "שתף",
"send-message-to": "אל:",
"message_title": "הזן את ההודעה לשליחה",
"send": "שלח",
"copy": "העתק",
"base64-title-files": "שתף קבצים",
"base64-title-text": "שתף טקסט",
"base64-processing": "מעבד…",
"base64-tap-to-paste": "גע כאן כדי לשתף {{type}}",
"base64-paste-to-send": "הדבק כאן כדי לשתף {{type}}",
"base64-files": "קבצים",
"file-other-description-image": "ותמונה 1 אחרת",
"file-other-description-file": "וקובץ 1 אחר",
"file-other-description-image-plural": "ו{{count}} תמונות אחרות",
"file-other-description-file-plural": "ו{{count}} קבצים אחרים",
"title-image": "תמונה",
"title-image-plural": "תמונות",
"language-selector-title": "הגדר שפה",
"pair-devices-qr-code_title": "לחץ כדי להעתיק את הקישור כדי לבצע תיאום עם מכשיר זה",
"approve": "אשר",
"share-text-title": "שתף הודעת טקסט",
"share-text-subtitle": "ערוך את ההודעה לפני השליחה:",
"close-toast_title": "סגירת ההתראה"
},
"about": {
"mastodon_title": "כתוב על PairDrop בMastodon",
"buy-me-a-coffee_title": "קנה לי קפה!",
"claim": "הדרך הקלה ביותר להעברת קבצים בין מכשירים",
"github_title": "PairDrop בGitHub",
"tweet_title": "צייץ על PairDrop",
"custom_title": "עקוב אחרינו",
"bluesky_title": "עקוב אחרינו בBlueSky",
"privacypolicy_title": "פתח את מדיניות הפרטיות שלנו",
"faq_title": "שאלות נפוצות",
"close-about_aria-label": "סגירת אודות PairDrop"
},
"notifications": {
"display-name-changed-permanently": "שם התצוגה משתנה לתמיד",
"download-successful": "{{descriptor}} הורד",
"notifications-permissions-error": "ההרשאה להתראות נחסמה עקב סגירת חלונית בקשת ההרשאה מספר פעמים. אתה יכול לאפס זאת במידע על דף זה שניתן לגישה באמצעות לחיצה על אייקון המנעול ליד שורת הכתובת.",
"click-to-show": "לחץ כדי להציג",
"connecting": "מתחבר…",
"online": "הנך מחובר שוב לאינטרנט",
"pairing-cleared": "כל ההתאמות למכשירים הוסרו",
"pair-url-copied-to-clipboard": "הקישור להתאמת המכשיר הזה הועתק",
"connected": "מחובר",
"online-requirement-pairing": "אתה צריך להיות מחובר לאינטרנט כדי לתאם מכשירים",
"online-requirement-public-room": "אתה צריך להיות מחובר כדי ליצור חדר ציבורי",
"files-incorrect": "הקבצים שגויים",
"unfinished-transfers-warning": "יש עוד העברות שלא הסתיימו. אתה בטוח שאתה רוצה לסגור את PairDrop?",
"rate-limit-join-key": "הגעת למגבלה. חכה 10 שניות ונסה שנית.",
"selected-peer-left": "המכשיר הנבחר עזב",
"display-name-changed-temporarily": "שם התצוגה משתנה להפעלה זו בלבד",
"display-name-random-again": "שם התצוגה נוצר שוב באופן אקראי",
"pairing-tabs-error": "תיאום בין שני כרטיסיות בדפדפן אינו אפשרי",
"pairing-success": "המכשירים תואמו",
"pairing-not-persistent": "המכשירים המתואמים אינם קבועים",
"pairing-key-invalid": "קוד שגוי",
"pairing-key-invalidated": "הקוד {{key}} לא תקף עוד",
"public-room-id-invalid": "מזהה חדר שגוי",
"public-room-left": "עזבת את החדר הציבורי {{publicRoomId}}",
"copied-to-clipboard": "הועתק",
"room-url-copied-to-clipboard": "הקישור לחדר הציבורי הועתק",
"copied-to-clipboard-error": "העתקה אינה אפשרית. העתק ידנית.",
"text-content-incorrect": "תוכן הטקסט שגוי",
"file-content-incorrect": "תוכן הקובץ שגוי",
"clipboard-content-incorrect": "התוכן שהודבק שגוי",
"notifications-enabled": "ההתראות אופשרו",
"link-received": "קישור התקבל מ{{name}} - לחץ כדי לפתוח",
"message-received": "הודעה התקבלה מ{{name}} - לחץ כדי להעתיק",
"click-to-download": "לחץ כדי להוריד",
"request-title": "{{name}} רוצה לשלוח {{count}} {{descriptor}}",
"copied-text": "הטקסט הועתק",
"copied-text-error": "ההעתקה נכשלה. העתק ידנית!",
"offline": "הנך לא מחובר לאינטרנט",
"file-transfer-completed": "העברת הקובץ הושלמה בהצלחה",
"ios-memory-limit": "שליחת קבצים למכשירי iOS אפשרית רק עד 200 MB בבת אחת",
"message-transfer-completed": "העברת ההודעה הושלמה בהצלחה"
},
"peer-ui": {
"click-to-send": "לחץ כדי לשלוח קבצים או בצע לחיצה ימנית כדי לשלוח הודעה",
"click-to-send-share-mode": "לחץ כדי לשלוח {{descriptor}}",
"connection-hash": "כדי לאמת את האבטחה של ההצפנה מצד לצד, השווה את מספר אבטחה זה בין שני המכשירים",
"preparing": "מתכונן…",
"waiting": "מחכה…",
"processing": "מעבד…",
"transferring": "מעביר…"
},
"document-titles": {
"file-received": "הקובץ התקבל",
"file-received-plural": "{{count}} קבצים התקבלו",
"file-transfer-requested": "העברת קבצים מתבקשת",
"image-transfer-requested": "העברת תמונות מתבקשת",
"message-received": "התקבלה הודעה",
"message-received-plural": "{{count}} הודעות התקבלו"
}
}
-184
View File
@@ -1,184 +0,0 @@
{
"header": {
"language-selector_title": "Nyelv beállítása",
"theme-auto_title": "Téma automatikusan rendszerhez igazítása",
"about_aria-label": "\"A PairDrop-ról\" megnyitása",
"theme-light_title": "Mindig világos téma használata",
"theme-dark_title": "Mindig sötét téma használata",
"notification_title": "Értesítések engedélyezése",
"install_title": "PairDrop telepítése",
"edit-paired-devices_title": "Párosított eszközök szerkeztése",
"cancel-share-mode": "Mégse",
"edit-share-mode": "Szerkesztés",
"expand_title": "Fejléc gombsorának kibővítése",
"about_title": "A PairDrop-ról",
"pair-device_title": "Eszközei végleges párosítása",
"join-public-room_title": "Ideiglenes csatlakozás a nyilvános szobához"
},
"instructions": {
"no-peers-title": "Nyissa meg a PairDrop-ot más eszközökön a fájlok küldéséhez",
"no-peers_data-drop-bg": "Engedje el a címzett kiválasztásához",
"x-instructions_desktop": "Kattintson a fájlküldéshez, vagy kattintson jobb gombbal üzenet küldéséhez",
"x-instructions_data-drop-peer": "Engedje el a partnernek való küldéshez",
"x-instructions_data-drop-bg": "Engedje el a címzett kiválasztásához",
"activate-share-mode-base": "Nyissa meg a PairDrop-ot más eszközökön a küldéséhez",
"activate-share-mode-and-other-file": "és egy másik fájl",
"activate-share-mode-and-other-files-plural": "és {{count}} másik fájl",
"activate-share-mode-shared-file": "megosztott fájl",
"activate-share-mode-shared-files-plural": "{{count}} megosztott fájl",
"x-instructions_mobile": "Koppintson a fájlküldéshez, vagy nyomja hosszan üzenet küldéséhez",
"x-instructions-share-mode_desktop": "Kattintson a {{descriptor}} küldéséhez",
"x-instructions-share-mode_mobile": "Koppintson a {{descriptor}} küldéséhez",
"activate-share-mode-shared-text": "megosztott szöveg",
"webrtc-requirement": "Ezen PairDrop példány használatához engedélyezni kell a WebRTC-t!",
"no-peers-subtitle": "Párosítsa eszközeit vagy lépjen be egy nyilvános szobába, hogy más hálózatokon is felfedezhető legyen"
},
"footer": {
"known-as": "Ön így látható:",
"display-name_data-placeholder": "Betöltés…",
"display-name_title": "Az eszköze nevének végleges megváltoztatása",
"discovery": "Ön felfedezhető:",
"on-this-network_title": "Mindenki által felfedezhető a hálózaton.",
"paired-devices": "a csatlakoztatott eszközök által",
"public-room-devices": "a {{roomId}} szobában",
"traffic": "A forgalom",
"routed": "a szerveren van átirányítva",
"webrtc": "ha nem elérhető a WebRTC.",
"on-this-network": "ezen a hálózaton",
"paired-devices_title": "A párosított eszközeid által minden esetben felfedezhető a hálózaton, a hálózattól függetlenül.",
"public-room-devices_title": "Mindenki által felfedezhető ebben a nyilvános szobában, a hálózattól függetlenül."
},
"dialogs": {
"input-key-on-this-device": "Írja be ezt a kódot egy másik eszközön",
"pair-devices-title": "Eszközök végleges párosítása",
"temporary-public-room-title": "Ideiglenes nyilvános szoba",
"input-room-id-on-another-device": "Írja be ezt a szobakódot egy másik eszközön",
"enter-room-id-from-another-device": "Írja be a szobakódot egy másik eszközről a szobához való csatlakozáshoz.",
"cancel": "Mégse",
"pair": "Párosítás",
"edit-paired-devices-title": "Párosított eszközök szerkesztése",
"unpair": "Párosítás megszüntetése",
"paired-device-removed": "Párosított eszköz eltávolítva.",
"paired-devices-wrapper_data-empty": "Nincs párosított eszköz.",
"auto-accept": "automatikus elfogadás",
"close": "Bezárás",
"join": "Csatlakozás",
"would-like-to-share": "szeretne megosztani",
"accept": "Elfogadás",
"has-sent": "küldött:",
"share": "Megosztás",
"send-message-title": "Üzenet küldése",
"send-message-to": "Neki:",
"receive-text-title": "Üzenet érkezett",
"base64-processing": "Feldolgozás…",
"scan-qr-code": "vagy olvassa be a QR-kódot.",
"auto-accept-instructions-1": "Aktiválás",
"auto-accept-instructions-2": "hogy automatikusan elfogadja az adott eszközről küldött összes fájlt.",
"leave": "Kilépés",
"decline": "Elutasítás",
"download": "Letöltés",
"send": "Küldés",
"message_title": "Írja be a küldeni kívánt üzenetet",
"message_placeholder": "Szöveg",
"copy": "Másolás",
"base64-title-files": "Fájlok megosztása",
"base64-title-text": "Szöveg megosztása",
"enter-key-from-another-device": "Ide írja be a kódot egy másik eszközről.",
"hr-or": "VAGY",
"base64-text": "szöveg",
"base64-tap-to-paste": "Koppintson ide a {{type}} megosztásához",
"base64-paste-to-send": "Illessze be a ide a vágólapja tartalmát a {{type}} megosztásához",
"base64-files": "fájlok",
"file-other-description-image": "és egy másik kép",
"file-other-description-file": "és egy másik fájl",
"file-other-description-image-plural": "és {{count}} másik kép",
"file-other-description-file-plural": "és {{count}} másik fájl",
"title-image": "Kép",
"title-file": "Fájl",
"title-image-plural": "Képek",
"title-file-plural": "Fájlok",
"receive-title": "{{descriptor}} érkezett",
"download-again": "Letöltés újra",
"language-selector-title": "Nyelv beállítása",
"system-language": "Rendszer nyelve",
"approve": "jóváhagyás",
"share-text-title": "Szöveges üzenet megosztása",
"share-text-subtitle": "Üzenet szerkesztése küldés előtt:",
"share-text-checkbox": "Mindig mutassa ezt a párbeszédablakot üzenet megosztásakor",
"close-toast_title": "Értesítés bezárása",
"public-room-qr-code_title": "Kattintson a szoba linkjének másolásához",
"pair-devices-qr-code_title": "Kattintson az eszköz párosításához való link másolásához"
},
"notifications": {
"online-requirement-pairing": "Online kell lennie készülékek párosításához",
"link-received": "Link érkezett tőle: {{name}} - Kattintson a megnyitáshoz",
"selected-peer-left": "A kiválasztott partner kilépett",
"display-name-changed-permanently": "A megjelenített neved véglegesen meg lett változtatva",
"download-successful": "{{descriptor}} letöltve",
"pairing-tabs-error": "Két böngészőlap párosítása lehetetlen",
"display-name-random-again": "A megjelenített neved véletlenszerűen újra lett generálva",
"pairing-key-invalid": "Érvénytelen kulcs",
"pairing-cleared": "Minden eszköz párosítása megszüntetve",
"public-room-id-invalid": "Érvénytelen szobakód",
"copied-to-clipboard": "Vágólapra másolva",
"copied-to-clipboard-error": "Másolás nem lehetséges. Manuális másolás szükséges.",
"text-content-incorrect": "A szöveg tartalma helytelen",
"file-content-incorrect": "A fájl tartalma helytelen",
"pair-url-copied-to-clipboard": "Az eszköz párosítására való link másolva a vágólapra",
"notifications-enabled": "Értesítések engedélyezve",
"request-title": "{{name}} szeretne küldeni: {{count}} {{descriptor}}",
"copied-text-error": "A vágólapra való írás nem sikerült. Manuális másolás szükséges.",
"click-to-download": "Kattintson a letöltéshez",
"click-to-show": "Kattintson a megjelenítéshez",
"connected": "Csatlakoztatva",
"online": "Újra online lett",
"connecting": "Csatlakozás…",
"files-incorrect": "A fájlok helytelenek",
"file-transfer-completed": "A fájlok küldése sikeres",
"message-transfer-completed": "Üzenet küldése sikeres",
"unfinished-transfers-warning": "Befejezetlen átvitelek folyamatban. Biztosan be akarja zárni a PairDrop-ot?",
"pairing-success": "Eszközök párosítva",
"pairing-not-persistent": "A párosított eszközök nem maradandóak",
"pairing-key-invalidated": "{{key}} kulcs érvénytelenítve",
"public-room-left": "{{publicRoomId}} szoba elhagyva",
"online-requirement-public-room": "Online kell lennie szoba készítéséhez",
"ios-memory-limit": "Egyszerre csak 200 MB-os fájlátvitel lehetséges iOS-re való küldéskor",
"room-url-copied-to-clipboard": "A nyilvános szoba linkje másolva a vágólapra",
"clipboard-content-incorrect": "A vágólap tartalma helytelen",
"copied-text": "Szöveg másolva a vágólapra",
"message-received": "Üzenet érkezett tőle: {{name}} - Kattintson a másoláshoz",
"notifications-permissions-error": "Az értesítések engedélye letiltásra került, mivel a felhasználó többször is elutasította az engedélykérést. Ez visszaállítható az oldalinformációkban, amely az URL-sáv melletti lakat ikonra kattintva érhet el.",
"offline": "Ön offline",
"rate-limit-join-key": "Az átviteli sebességhatár elérte a határt. Várjon 10 másodpercet, és próbálja meg újra.",
"display-name-changed-temporarily": "A megjelenített neved meg lett változtatva csak erre a munkamenetre"
},
"about": {
"close-about_aria-label": "A PairDrop-ról bezárása",
"github_title": "PairDrop a GitHub-on",
"buy-me-a-coffee_title": "Vegyél nekem egy kávét!",
"mastodon_title": "Írj a PairDrop-ról Mastodon-on",
"bluesky_title": "Kövess minket a BlueSky-on",
"faq_title": "Gyakran ismételt kérdések",
"claim": "Az eszközök közötti fájlátvitel legegyszerűbb módja",
"tweet_title": "Tweetelj a PairDrop-ról",
"custom_title": "Kövess minket",
"privacypolicy_title": "Nyisd meg az adatvédelmi szabályzatunkat"
},
"document-titles": {
"file-received": "Fájl érkezett",
"file-received-plural": "{{count}} fájl érkezett",
"image-transfer-requested": "Képátvitel kérelmezve",
"message-received": "Üzenet érkezett",
"message-received-plural": "{{count}} üzenet érkezett",
"file-transfer-requested": "Fájlátvitel kérelmezve"
},
"peer-ui": {
"preparing": "Előkészítés…",
"click-to-send-share-mode": "Kattintson a {{descriptor}} küldéséhez",
"waiting": "Várakozás…",
"processing": "Feldolgozás…",
"transferring": "Átvitel…",
"click-to-send": "Kattintson fájlok küldéséhez vagy kattintson jobb gombbal üzenet küldéséhez",
"connection-hash": "A végpontok közötti titkosítás biztonságának ellenőrzéséhez hasonlítsa össze ezt a biztonsági számot mindkét eszközön"
}
}
+2 -2
View File
@@ -74,9 +74,9 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan", "x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan",
"x-instructions-share-mode_desktop": "Klik untuk mengirim {{descriptor}}", "x-instructions-share-mode_desktop": "Klik untuk mengirim",
"activate-share-mode-and-other-files-plural": "dan {{count}} file lainnya", "activate-share-mode-and-other-files-plural": "dan {{count}} file lainnya",
"x-instructions-share-mode_mobile": "Ketuk untuk mengirim {{descriptor}}", "x-instructions-share-mode_mobile": "Ketuk untuk mengirim",
"activate-share-mode-base": "Buka PairDrop di perangkat lain untuk berkirim", "activate-share-mode-base": "Buka PairDrop di perangkat lain untuk berkirim",
"no-peers-subtitle": "Pasangkan perangkat atau masuk ke ruang publik agar dapat terdeteksi di jaringan lain", "no-peers-subtitle": "Pasangkan perangkat atau masuk ke ruang publik agar dapat terdeteksi di jaringan lain",
"activate-share-mode-shared-text": "teks bersama", "activate-share-mode-shared-text": "teks bersama",
+33 -51
View File
@@ -5,9 +5,9 @@
"display-name_data-placeholder": "Caricamento…", "display-name_data-placeholder": "Caricamento…",
"display-name_title": "Modifica il nome del tuo dispositivo permanentemente", "display-name_title": "Modifica il nome del tuo dispositivo permanentemente",
"traffic": "Il traffico è", "traffic": "Il traffico è",
"paired-devices_title": "Puoi essere rilevato dai dispositivi associati in ogni momento, indipendentemente dalla rete.", "paired-devices_title": "Puoi essere rilevato dai dispositivi abbinati in ogni momento, indipendentemente dalla rete.",
"public-room-devices": "nella stanza {{roomId}}", "public-room-devices": "nella stanza {{roomId}}",
"paired-devices": "da dispositivi associati", "paired-devices": "da dispositivi abbinati",
"on-this-network": "su questa rete", "on-this-network": "su questa rete",
"routed": "instradato attraverso il server", "routed": "instradato attraverso il server",
"discovery": "Puoi essere rilevato:", "discovery": "Puoi essere rilevato:",
@@ -15,7 +15,7 @@
"known-as": "Sei visibile come:" "known-as": "Sei visibile come:"
}, },
"header": { "header": {
"cancel-share-mode": "Annulla", "cancel-share-mode": "Fatto",
"theme-auto_title": "Adatta il tema al sistema automaticamente", "theme-auto_title": "Adatta il tema al sistema automaticamente",
"install_title": "Installa PairDrop", "install_title": "Installa PairDrop",
"theme-dark_title": "Usa sempre il tema scuro", "theme-dark_title": "Usa sempre il tema scuro",
@@ -26,15 +26,13 @@
"language-selector_title": "Imposta Lingua", "language-selector_title": "Imposta Lingua",
"about_title": "Informazioni su PairDrop", "about_title": "Informazioni su PairDrop",
"about_aria-label": "Apri Informazioni su PairDrop", "about_aria-label": "Apri Informazioni su PairDrop",
"theme-light_title": "Usa sempre il tema chiaro", "theme-light_title": "Usa sempre il tema chiaro"
"edit-share-mode": "Modifica",
"expand_title": "Espandi la riga dei pulsanti nell'intestazione"
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio", "x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio",
"x-instructions-share-mode_desktop": "Clicca per inviare {{descriptor}}", "x-instructions-share-mode_desktop": "Clicca per inviare",
"activate-share-mode-and-other-files-plural": "e altri {{count}} files", "activate-share-mode-and-other-files-plural": "e altri {{count}} files",
"x-instructions-share-mode_mobile": "Tocca per inviare {{descriptor}}", "x-instructions-share-mode_mobile": "Tocca per inviare",
"activate-share-mode-base": "Apri PairDrop su altri dispositivi per inviare", "activate-share-mode-base": "Apri PairDrop su altri dispositivi per inviare",
"no-peers-subtitle": "Abbina dispositivi o entra in una stanza pubblica per essere rilevabile su altre reti", "no-peers-subtitle": "Abbina dispositivi o entra in una stanza pubblica per essere rilevabile su altre reti",
"activate-share-mode-shared-text": "testo condiviso", "activate-share-mode-shared-text": "testo condiviso",
@@ -43,26 +41,23 @@
"x-instructions_data-drop-peer": "Rilascia per inviare al peer", "x-instructions_data-drop-peer": "Rilascia per inviare al peer",
"x-instructions_data-drop-bg": "Rilascia per selezionare il destinatario", "x-instructions_data-drop-bg": "Rilascia per selezionare il destinatario",
"no-peers_data-drop-bg": "Rilascia per selezionare il destinatario", "no-peers_data-drop-bg": "Rilascia per selezionare il destinatario",
"webrtc-requirement": "Per usare questa istanza di PairDrop, devi attivare WebRTC!", "webrtc-requirement": "Per usare questa istanza di PairDrop, devi attivare WebRTC!"
"activate-share-mode-shared-file": "file condiviso",
"activate-share-mode-shared-files-plural": "{{count}} files condivisi",
"activate-share-mode-and-other-file": "ed 1 altro file"
}, },
"dialogs": { "dialogs": {
"auto-accept-instructions-2": "per accettare automaticamente tutti i files inviati da quel dispositivo.", "auto-accept-instructions-2": "per accettare automaticamente tutti i files inviati da quel dispositivo.",
"edit-paired-devices-title": "Modifica Dispositivi Associati", "edit-paired-devices-title": "Modifica Dispositivi Abbinati",
"cancel": "Annulla", "cancel": "Annulla",
"auto-accept-instructions-1": "Attiva", "auto-accept-instructions-1": "Attiva",
"pair-devices-title": "Associa Dispositivi Permanentemente", "pair-devices-title": "Abbina Dispositivi Permanentemente",
"temporary-public-room-title": "Stanza Pubblica Temporanea", "temporary-public-room-title": "Stanza Pubblica Temporanea",
"close": "Chiudi", "close": "Chiudi",
"unpair": "Dissocia", "unpair": "Dissocia",
"pair": "Associa", "pair": "Abbina",
"scan-qr-code": "o scannerizza il codice QR.", "scan-qr-code": "o scannerizza il codice QR.",
"input-key-on-this-device": "Inserisci questo codice su un altro dispositivo", "input-key-on-this-device": "Inserisci questo codice su un altro dispositivo",
"paired-devices-wrapper_data-empty": "Nessun dispositivo associato.", "paired-devices-wrapper_data-empty": "Nessun dispositivo abbinato.",
"enter-key-from-another-device": "Inserisci il codice dell'altro dispositivo qui.", "enter-key-from-another-device": "Inserisci il codice dell'altro dispositivo qui.",
"auto-accept": "accetta automaticamente", "auto-accept": "accetta-automaticamente",
"input-room-id-on-another-device": "Inserisci l'ID di questa stanza su un altro dispositivo", "input-room-id-on-another-device": "Inserisci l'ID di questa stanza su un altro dispositivo",
"enter-room-id-from-another-device": "Inserisci l'ID stanza da un altro dispositivo per accedere alla stanza.", "enter-room-id-from-another-device": "Inserisci l'ID stanza da un altro dispositivo per accedere alla stanza.",
"base64-paste-to-send": "Incolla qui per inviare {{type}}", "base64-paste-to-send": "Incolla qui per inviare {{type}}",
@@ -76,7 +71,7 @@
"join": "Unisciti", "join": "Unisciti",
"title-image-plural": "Immagini", "title-image-plural": "Immagini",
"send": "Invia", "send": "Invia",
"base64-tap-to-paste": "Tocca qui per condividere {{type}}", "base64-tap-to-paste": "Tocca qui per incollare {{type}}",
"base64-text": "testo", "base64-text": "testo",
"copy": "Copia", "copy": "Copia",
"file-other-description-image": "e 1 altra immagine", "file-other-description-image": "e 1 altra immagine",
@@ -87,7 +82,7 @@
"title-image": "Immagine", "title-image": "Immagine",
"file-other-description-file-plural": "e altri {{count}} files", "file-other-description-file-plural": "e altri {{count}} files",
"would-like-to-share": "vorrebbe condividere", "would-like-to-share": "vorrebbe condividere",
"send-message-to": "A:", "send-message-to": "Invia un messaggio a",
"language-selector-title": "Imposta Lingua", "language-selector-title": "Imposta Lingua",
"hr-or": "OPPURE", "hr-or": "OPPURE",
"download-again": "Scarica ancora", "download-again": "Scarica ancora",
@@ -97,20 +92,11 @@
"send-message-title": "Invia Messaggio", "send-message-title": "Invia Messaggio",
"file-other-description-image-plural": "e {{count}} altre immagini", "file-other-description-image-plural": "e {{count}} altre immagini",
"message_title": "Inserire messaggio da inviare", "message_title": "Inserire messaggio da inviare",
"pair-devices-qr-code_title": "Clicca per copiare il link di associazione a questo dispositivo", "pair-devices-qr-code_title": "Clicca per copiare il link di abbinamento di questo dispositivo",
"public-room-qr-code_title": "Clicca per copirare il link della stanza pubblica", "public-room-qr-code_title": "Clicca per copirare il link della stanza pubblica"
"message_placeholder": "Testo",
"paired-device-removed": "Il dispositivo associato è stato rimosso.",
"base64-title-files": "Condividi Files",
"base64-title-text": "Condividi Testo",
"share-text-subtitle": "Modifica messaggio prima dell'invio:",
"share-text-checkbox": "Mostra sempre questa casella di dialogo quando si condivide del testo",
"approve": "accetta",
"share-text-title": "Condividi Messaggio di Testo",
"close-toast_title": "Chiudi notifica"
}, },
"notifications": { "notifications": {
"request-title": "{{name}} vorrebbe inviare {{count}} {{descriptor}}", "request-title": "{{name}} vorrebbe trasferire {{count}} {{descriptor}}",
"unfinished-transfers-warning": "Ci sono dei trasferimenti in corso. Sei sicuro di voler chiudere PairDrop?", "unfinished-transfers-warning": "Ci sono dei trasferimenti in corso. Sei sicuro di voler chiudere PairDrop?",
"message-received": "Messaggio ricevuto da {{name}} - Clicca per copiare", "message-received": "Messaggio ricevuto da {{name}} - Clicca per copiare",
"rate-limit-join-key": "Limite raggiunto. Aspetta 10 secondi e riprova.", "rate-limit-join-key": "Limite raggiunto. Aspetta 10 secondi e riprova.",
@@ -118,22 +104,22 @@
"pairing-key-invalidated": "Il codice {{key}} è stato invalidato", "pairing-key-invalidated": "Il codice {{key}} è stato invalidato",
"pairing-key-invalid": "Codice non valido", "pairing-key-invalid": "Codice non valido",
"connected": "Connesso", "connected": "Connesso",
"pairing-not-persistent": "I dispositivi associati non sono permanenti", "pairing-not-persistent": "I dispositivi abbinati non sono persistenti",
"text-content-incorrect": "Il contenuto di testo è errato", "text-content-incorrect": "Il contenuto testuale non è corretto",
"message-transfer-completed": "Trasferimento del messaggio completato", "message-transfer-completed": "Trasferimento del messaggio completato",
"file-transfer-completed": "Trasferimento file completato", "file-transfer-completed": "Trasferimento file completato",
"file-content-incorrect": "Il contenuto del file è errato", "file-content-incorrect": "Il contenuto del file non è corretto",
"files-incorrect": "I file sono errati", "files-incorrect": "I file non sono corretti",
"selected-peer-left": "Il peer selezionato ha abbandonato", "selected-peer-left": "Peer selezionato ha abbandonato",
"link-received": "Link ricevuto da {{name}} - Clicca per aprire", "link-received": "Link ricevuto da {{name}} - Clicca per aprire",
"online": "Sei di nuovo online", "online": "Sei di nuovo online",
"public-room-left": "Hai abbandonato la stanza pubblica {{publicRoomId}}", "public-room-left": "Ha lasciato la stanza pubblica {{publicRoomId}}",
"copied-text": "Testo copiato negli appunti", "copied-text": "Testo copiato negli appunti",
"display-name-random-again": "Il nome visualizzato viene di nuovo generato casualmente", "display-name-random-again": "Il nome visualizzato è generato casualmente un'altra volta",
"display-name-changed-permanently": "Il nome visualizzato è cambiato definitivamente", "display-name-changed-permanently": "Il nome visualizzato è cambiato permanentemente",
"copied-to-clipboard-error": "La funzione di copia non è possibile. Copia manualmente.", "copied-to-clipboard-error": "La copia non è possibile. Copia manualmente.",
"pairing-success": "Dispositivi associati", "pairing-success": "Dispositivi abbinati",
"clipboard-content-incorrect": "Il contenuto copiato è errato", "clipboard-content-incorrect": "Il contenuto copiato non è corretto",
"display-name-changed-temporarily": "Il nome visualizzato è cambiato solo per questa sessione", "display-name-changed-temporarily": "Il nome visualizzato è cambiato solo per questa sessione",
"copied-to-clipboard": "Copiato negli appunti", "copied-to-clipboard": "Copiato negli appunti",
"offline": "Sei offline", "offline": "Sei offline",
@@ -142,13 +128,13 @@
"click-to-download": "Clicca per scaricare", "click-to-download": "Clicca per scaricare",
"pairing-cleared": "Tutti i dispositivi sono stati dissociati", "pairing-cleared": "Tutti i dispositivi sono stati dissociati",
"notifications-enabled": "Notifiche attivate", "notifications-enabled": "Notifiche attivate",
"online-requirement-pairing": "Devi essere online per associare dispositivi", "online-requirement-pairing": "Devi essere online per abbinare dispositivi",
"online-requirement-public-room": "Devi essere online per creare una stanza pubblica", "online-requirement-public-room": "Devi essere online per creare una stanza pubblica",
"copied-text-error": "Scrittura negli appunti fallita. Copia manualmente!", "copied-text-error": "Scrittura negli appunti fallita. Copia manualmente!",
"download-successful": "{{descriptor}} scaricato", "download-successful": "{{descriptor}} scaricato",
"click-to-show": "Clicca per mostrare", "click-to-show": "Clicca per mostrare",
"notifications-permissions-error": "Il permesso all'invio delle notifiche è stato negato poiché l'utente ha ignorato varie volte le richieste di permesso. Ciò può essere ripristinato nelle \"informazioni sito\" cliccando sull'icona a forma di lucchetto vicino alla barra degli indirizzi.", "notifications-permissions-error": "Il permesso all'invio delle notifiche è stato negato poiché l'utente ha ignorato varie volte le richieste di permesso. Ciò può essere ripristinato nelle \"informazioni sito\" cliccando sull'icona a forma di lucchetto vicino alla barra degli indirizzi.",
"pair-url-copied-to-clipboard": "Link di associazione copiato negli appunti", "pair-url-copied-to-clipboard": "Link di abbinamento copiato negli appunti",
"room-url-copied-to-clipboard": "Link della stanza copiato negli appunti" "room-url-copied-to-clipboard": "Link della stanza copiato negli appunti"
}, },
"peer-ui": { "peer-ui": {
@@ -164,18 +150,14 @@
"claim": "Il modo più semplice per trasferire files tra dispositivi", "claim": "Il modo più semplice per trasferire files tra dispositivi",
"tweet_title": "Twitta riguardo PairDrop", "tweet_title": "Twitta riguardo PairDrop",
"close-about_aria-label": "Chiudi Informazioni su PairDrop", "close-about_aria-label": "Chiudi Informazioni su PairDrop",
"buy-me-a-coffee_title": "Offrimi un caffè!", "buy-me-a-coffee_title": "Comprami un caffè!",
"github_title": "PairDrop su GitHub", "github_title": "PairDrop su GitHub",
"faq_title": "Domande Frequenti", "faq_title": "Domande Frequenti"
"mastodon_title": "Scrivi su Mastodon di PairDrop",
"bluesky_title": "Seguici su BlueSky",
"custom_title": "Seguici",
"privacypolicy_title": "Apri la nostra policy sulla privacy"
}, },
"document-titles": { "document-titles": {
"file-transfer-requested": "Trasferimento File Richiesto", "file-transfer-requested": "Trasferimento File Richiesto",
"image-transfer-requested": "Trasferimento Immagine Richiesto", "image-transfer-requested": "Trasferimento Immagine Richiesto",
"message-received-plural": "{{count}} Messaggi Ricevuti", "message-received-plural": "{{count}} Messaggi ricevuti",
"message-received": "Messaggio ricevuto", "message-received": "Messaggio ricevuto",
"file-received": "File Ricevuto", "file-received": "File Ricevuto",
"file-received-plural": "{{count}} Files Ricevuti" "file-received-plural": "{{count}} Files Ricevuti"
+76 -76
View File
@@ -1,17 +1,17 @@
{ {
"footer": { "footer": {
"webrtc": "(WebRTCが無効なため)", "webrtc": "WebRTCが利用できない場合。",
"public-room-devices_title": "公開ルーム内のデバイスは、接続中のネットワークと関係なくアクセスできます。", "public-room-devices_title": "のデバイスはネットワークと関係なく、このパブリックルームのデバイスにより発見される可能性があります。",
"display-name_data-placeholder": "読み込み中…", "display-name_data-placeholder": "読み込み中…",
"display-name_title": "デバイス名を変更する", "display-name_title": "永続的なデバイス名を編集する",
"traffic": "この通信は", "traffic": "トラフィックは",
"paired-devices_title": "ペアリング済みデバイスであれば、接続中のネットワークに関わらずアクセスできます。", "paired-devices_title": "このデバイスはネットワークと関係なく、常にペア設定したデバイスにより発見される可能性があります。",
"public-room-devices": "ルーム{{roomId}}", "public-room-devices": "ルーム{{roomId}}",
"paired-devices": "ペアリング済みデバイス", "paired-devices": "ペア設定したデバイス",
"on-this-network": "このネットワーク上", "on-this-network": "このネットワーク上",
"routed": "サーバーを経由します", "routed": "サーバーを経由します",
"discovery": "このデバイスを検出可能なネットワーク:", "discovery": "このデバイスは以下で発見される可能性があります:",
"on-this-network_title": "このネットワーク上のすべてのデバイスからアクセスできます。", "on-this-network_title": "このデバイスはこのネットワーク上の誰にでも発見される可能性があります。",
"known-as": "他のデバイスに表示される名前:" "known-as": "他のデバイスに表示される名前:"
}, },
"notifications": { "notifications": {
@@ -23,49 +23,49 @@
"pairing-key-invalidated": "コード{{key}}が失効しました", "pairing-key-invalidated": "コード{{key}}が失効しました",
"pairing-key-invalid": "無効なコード", "pairing-key-invalid": "無効なコード",
"connected": "接続しました", "connected": "接続しました",
"pairing-not-persistent": "このデバイスとのペアリングは解除される可能性があります", "pairing-not-persistent": "ペア設定されたデバイスは永続化されていません",
"text-content-incorrect": "無効なテキスト内容です", "text-content-incorrect": "テキスト内容が不正です",
"message-transfer-completed": "メッセージの送信が完了しました", "message-transfer-completed": "メッセージの送信が完了しました",
"file-transfer-completed": "ファイル転送が完了しました", "file-transfer-completed": "ファイル転送が完了しました",
"file-content-incorrect": "無効なファイル内容です", "file-content-incorrect": "ファイル内容が不正です",
"files-incorrect": "ファイルが間違っています", "files-incorrect": "ファイルが間違っています",
"selected-peer-left": "選択したデバイスが退出しました", "selected-peer-left": "選択した相手が退出しました",
"link-received": "{{name}}から受信したリンク(クリックして開く)", "link-received": "{{name}}から受信したリンク(クリックして開く)",
"online": "オンラインに復帰しました", "online": "オンラインに復帰しました",
"public-room-left": "公開ルーム{{publicRoomId}}から退出しました", "public-room-left": "パブリックルーム{{publicRoomId}}から退出しました",
"copied-text": "テキストをクリップボードにコピーしました", "copied-text": "テキストをクリップボードにコピーしました",
"display-name-random-again": "新しいランダムなデバイス名に変更されました", "display-name-random-again": "表示名がもう一度ランダムに生成されました",
"display-name-changed-permanently": "デバイス名が変更されました", "display-name-changed-permanently": "永続的な表示名が変更されました",
"copied-to-clipboard-error": "コピーできませんでした。手動でコピーしてください。", "copied-to-clipboard-error": "コピーできませんでした。手動でコピーしてください。",
"pairing-success": "ペアリングしました", "pairing-success": "デバイスがペア設定されました",
"clipboard-content-incorrect": "無効なクリップボード内容です", "clipboard-content-incorrect": "クリップボード内容が不正です",
"display-name-changed-temporarily": "この接続のみデバイス名が変更されました", "display-name-changed-temporarily": "このセッションでの表示名が変更されました",
"copied-to-clipboard": "クリップボードにコピーしました", "copied-to-clipboard": "クリップボードにコピーしました",
"offline": "オフラインです", "offline": "オフラインです",
"pairing-tabs-error": "同じWebブラウザーで開いたタブ同士でペアリングすることはできません", "pairing-tabs-error": "同じWebブラウザーの2つのタブをペア設定することはできません",
"public-room-id-invalid": "無効なルームID", "public-room-id-invalid": "無効なルームID",
"click-to-download": "クリックしてダウンロード", "click-to-download": "クリックしてダウンロード",
"pairing-cleared": "全てのデバイスのペアリングを解除しました", "pairing-cleared": "全てのデバイスのペア設定を解除しました",
"notifications-enabled": "通知が有効になりました", "notifications-enabled": "通知が有効です",
"online-requirement-pairing": "ペアリングするにはオンラインである必要があります", "online-requirement-pairing": "デバイスをペア設定するにはオンラインである必要があります",
"online-requirement-public-room": "公開ルームを作成するにはオンラインである必要があります", "online-requirement-public-room": "パブリックルームを作成するにはオンラインである必要があります",
"copied-text-error": "クリップボードにコピーできませんでした。手動でコピーしてください。", "copied-text-error": "クリップボードにコピーできませんでした。手動でコピーしてください。",
"download-successful": "{{descriptor}}をダウンロードしました", "download-successful": "{{descriptor}}をダウンロードしました",
"click-to-show": "クリックして表示", "click-to-show": "クリックして表示",
"notifications-permissions-error": "通知がブロックされました。これはユーザーが通知許可を何度も拒否したためです。これをリセットするには、URLバーのにある鍵アイコンをクリックして、ページ情報にアクセスしてください。", "notifications-permissions-error": "ユーザーが権限のプロンプトを何度か閉じたため、通知の権限がブロックされました。これは、URL バーのにある鍵アイコンをクリックしてアクセスできるページ情報からリセットできます。",
"pair-url-copied-to-clipboard": "このデバイスペアリングするリンクをクリップボードにコピーしました", "pair-url-copied-to-clipboard": "このデバイスペア設定するリンクをクリップボードにコピーしました",
"room-url-copied-to-clipboard": "公開ルームへのリンクをクリップボードにコピーしました" "room-url-copied-to-clipboard": "パブリックルームへのリンクをクリップボードにコピーしました"
}, },
"header": { "header": {
"cancel-share-mode": "キャンセル", "cancel-share-mode": "完了",
"theme-auto_title": "システムテーマに合わせる", "theme-auto_title": "テーマをシステムの設定に自動的に合わせる",
"install_title": "PairDropをインストール", "install_title": "PairDropをインストール",
"theme-dark_title": "常にダークテーマを使用する", "theme-dark_title": "常にダークテーマを使用する",
"pair-device_title": "のデバイスとペアリングする", "pair-device_title": "あなたのデバイスを永続的にペア設定する",
"join-public-room_title": "公開ルームに参加する", "join-public-room_title": "パブリックルームに一時的に参加する",
"notification_title": "通知を有効にする", "notification_title": "通知を有効にする",
"edit-paired-devices_title": "ペアリング設定", "edit-paired-devices_title": "ペア設定したデバイスを編集する",
"language-selector_title": "言語設定", "language-selector_title": "言語設定",
"about_title": "PairDropについて", "about_title": "PairDropについて",
"about_aria-label": "PairDropについてを開く", "about_aria-label": "PairDropについてを開く",
"theme-light_title": "常にライトテーマを使用する", "theme-light_title": "常にライトテーマを使用する",
@@ -73,19 +73,19 @@
"expand_title": "ヘッダーボタン列を拡大する" "expand_title": "ヘッダーボタン列を拡大する"
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "タップファイルを送信長押しメッセージを送信します", "x-instructions_mobile": "タップしてファイルを送信または長押ししてメッセージを送信します",
"x-instructions-share-mode_desktop": "クリックして{{descriptor}}を送信", "x-instructions-share-mode_desktop": "クリックして送信",
"activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル", "activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル",
"x-instructions-share-mode_mobile": "タップして{{descriptor}}を送信", "x-instructions-share-mode_mobile": "タップして送信",
"activate-share-mode-base": "他のデバイスでPairDropを開いて送信します", "activate-share-mode-base": "他のデバイスでPairDropを開いて送信します",
"no-peers-subtitle": "ペアリングや公開ルームを使用すると、他のネットワーク上のデバイスと共有できます", "no-peers-subtitle": "デバイスをペア設定するかパブリックルームに参加すると、他のネットワーク上からも見つけられるようになります",
"activate-share-mode-shared-text": "共有されたテキスト", "activate-share-mode-shared-text": "共有されたテキスト",
"x-instructions_desktop": "左クリックファイルを送信右クリックメッセージを送信します", "x-instructions_desktop": "左クリックしてファイルを送信または右クリックしてメッセージを送信します",
"no-peers-title": "ファイルを共有するには他のデバイスでPairDropを開いてください", "no-peers-title": "他のデバイスでPairDropを開いてファイルを送信します",
"x-instructions_data-drop-peer": "ドロップするとこのデバイスに送信します", "x-instructions_data-drop-peer": "離すとこの相手に送信します",
"x-instructions_data-drop-bg": "送信したいデバイスの上でドロップしてください", "x-instructions_data-drop-bg": "送信したい相手の上で離してください",
"no-peers_data-drop-bg": "送信したいデバイスの上でドロップしてください", "no-peers_data-drop-bg": "送信したい相手の上で離してください",
"activate-share-mode-and-other-file": "とその他1個のファイル", "activate-share-mode-and-other-file": "もう1つの別のファイル",
"activate-share-mode-shared-file": "共有されたファイル", "activate-share-mode-shared-file": "共有されたファイル",
"activate-share-mode-shared-files-plural": "{{count}}個の共有されたファイル", "activate-share-mode-shared-files-plural": "{{count}}個の共有されたファイル",
"webrtc-requirement": "このPairDropインスタンスを使用するには、WebRTCを有効にする必要があります!" "webrtc-requirement": "このPairDropインスタンスを使用するには、WebRTCを有効にする必要があります!"
@@ -93,20 +93,20 @@
"peer-ui": { "peer-ui": {
"processing": "処理中…", "processing": "処理中…",
"click-to-send-share-mode": "クリックして{{descriptor}}を送信", "click-to-send-share-mode": "クリックして{{descriptor}}を送信",
"click-to-send": "クリックファイルを送信右クリックメッセージを送信します", "click-to-send": "クリックしてファイルを送信または右クリックしてメッセージを送信します",
"waiting": "待機中…", "waiting": "待機中…",
"connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認してください", "connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認します",
"preparing": "準備中…", "preparing": "準備中…",
"transferring": "転送中…" "transferring": "転送中…"
}, },
"dialogs": { "dialogs": {
"base64-paste-to-send": "ここをタップして{{type}}を送信", "base64-paste-to-send": "ここをタップして{{type}}を送信",
"auto-accept-instructions-2": "」を有効にすると、そのデバイスから送信されたすべてのファイルを自動的に受け入れます。", "auto-accept-instructions-2": "そのデバイスから送信される全てのファイルを自動的に承諾します。",
"receive-text-title": "メッセージを受信しました", "receive-text-title": "メッセージを受信しました",
"edit-paired-devices-title": "ペアリング設定", "edit-paired-devices-title": "ペア設定したデバイスを編集",
"cancel": "キャンセル", "cancel": "キャンセル",
"auto-accept-instructions-1": "", "auto-accept-instructions-1": "有効化",
"pair-devices-title": "新規ペアリング", "pair-devices-title": "デバイスを永続的にペア設定",
"download": "ダウンロード", "download": "ダウンロード",
"title-file": "ファイル", "title-file": "ファイル",
"base64-processing": "処理中…", "base64-processing": "処理中…",
@@ -116,58 +116,58 @@
"join": "参加", "join": "参加",
"title-image-plural": "複数の画像", "title-image-plural": "複数の画像",
"send": "送信", "send": "送信",
"base64-tap-to-paste": "ここをタップして{{type}}を共有", "base64-tap-to-paste": "ここをタップして{{type}}を貼り付け",
"base64-text": "テキスト", "base64-text": "テキスト",
"copy": "コピー", "copy": "コピー",
"file-other-description-image": "とその他1個の画像", "file-other-description-image": "と1個の他の画像",
"temporary-public-room-title": "公開ルーム", "temporary-public-room-title": "一時的なパブリックルーム",
"base64-files": "ファイル", "base64-files": "ファイル",
"has-sent": "が送信しました:", "has-sent": "が送信しました:",
"file-other-description-file": "とその他1個のファイル", "file-other-description-file": "と1個の他のファイル",
"close": "閉じる", "close": "閉じる",
"system-language": "システム言語", "system-language": "システム言語",
"unpair": "ペアリング解除", "unpair": "ペア解除",
"title-image": "画像", "title-image": "画像",
"file-other-description-file-plural": "とその他{{count}}個のファイル", "file-other-description-file-plural": "と{{count}}個の他のファイル",
"would-like-to-share": "が以下のファイルを共有しようとしています", "would-like-to-share": "が以下のファイルを共有しようとしています",
"send-message-to": "このデバイスにメッセージを送信:", "send-message-to": "このデバイスにメッセージを送信:",
"language-selector-title": "言語設定", "language-selector-title": "言語設定",
"pair": "ペアリング", "pair": "ペア設定",
"hr-or": "または", "hr-or": "または",
"scan-qr-code": "もしくはQRコードをスキャンしてください。", "scan-qr-code": "もしくはQRコードをスキャンします。",
"input-key-on-this-device": "このコードを他のデバイスに入力する", "input-key-on-this-device": "このキーを他のデバイスに入力する",
"download-again": "もう一度ダウンロードする", "download-again": "もう一度ダウンロードする",
"accept": "承諾", "accept": "承諾",
"paired-devices-wrapper_data-empty": "ペアリングしたデバイスはありません。", "paired-devices-wrapper_data-empty": "ペア設定したデバイスはありません。",
"enter-key-from-another-device": "他のデバイスに表示されたコードを入力してください。", "enter-key-from-another-device": "他のデバイスに表示されたキーをここに入力します。",
"share": "共有", "share": "共有",
"auto-accept": "自動承諾", "auto-accept": "自動承諾",
"title-file-plural": "複数のファイル", "title-file-plural": "複数のファイル",
"send-message-title": "メッセージを送信", "send-message-title": "メッセージを送信",
"input-room-id-on-another-device": "このルームIDを他のデバイスに入力するか", "input-room-id-on-another-device": "このルームIDを他のデバイスに入力",
"file-other-description-image-plural": "とその他{{count}}個の画像", "file-other-description-image-plural": "と{{count}}個の他の画像",
"enter-room-id-from-another-device": "他のデバイスに表示されたルームIDを入力してください。", "enter-room-id-from-another-device": "他のデバイスに表示された参加したいルームIDを入力します。",
"message_title": "送信するメッセージを挿入", "message_title": "送信するメッセージを挿入",
"pair-devices-qr-code_title": "クリックしてこのデバイスペアリングするリンクをコピー", "pair-devices-qr-code_title": "クリックしてこのデバイスペア設定するリンクをコピー",
"public-room-qr-code_title": "クリックして公開ルームへのリンクをコピー", "public-room-qr-code_title": "クリックしてパブリックルームへのリンクをコピー",
"paired-device-removed": "ペアリングを解除しました。", "paired-device-removed": "ペア設定されたデバイスが削除されました。",
"message_placeholder": "テキスト", "message_placeholder": "テキスト",
"base64-title-files": "共有されたファイル", "base64-title-files": "共有されたファイル",
"base64-title-text": "テキストを共有", "base64-title-text": "テキストを共有",
"approve": "OK", "approve": "承諾",
"share-text-subtitle": "送信する前にメッセージを編集する:", "share-text-subtitle": "送信する前にメッセージを編集する:",
"share-text-checkbox": "テキスト共有する際、毎回この画面を表示する", "share-text-checkbox": "テキスト共有するときに常にこのダイアログを表示する",
"close-toast_title": "通知を閉じる", "close-toast_title": "通知を閉じる",
"share-text-title": "テキストメッセージを共有します" "share-text-title": "テキストメッセージを共有します"
}, },
"about": { "about": {
"claim": "デバイス間ファイル共有を手軽に実現します", "claim": "デバイス間ファイルを転送する最も簡単な方法",
"tweet_title": "PairDropのことをポストする", "tweet_title": "PairDropについてツイートする",
"close-about_aria-label": "PairDropについてを閉じる", "close-about_aria-label": "PairDropについてを閉じる",
"buy-me-a-coffee_title": "コーヒーを一杯おごってください!", "buy-me-a-coffee_title": "コーヒーをおごってください!",
"github_title": "PairDrop on GitHub", "github_title": "GitHubでPairDropを見る",
"faq_title": "FAQ", "faq_title": "FAQ",
"mastodon_title": "MastodonにPairDropのことをトゥートする", "mastodon_title": "MastodonにPairDropについて書く",
"bluesky_title": "BlueSkyでフォロー", "bluesky_title": "BlueSkyでフォロー",
"custom_title": "フォロー", "custom_title": "フォロー",
"privacypolicy_title": "プライバシーポリシーを開く" "privacypolicy_title": "プライバシーポリシーを開く"
-29
View File
@@ -1,29 +0,0 @@
{
"header": {
"notification_title": "알림 켜기",
"edit-paired-devices_title": "연결된 기기 편집하기",
"install_title": "PairDrop 설치",
"theme-dark_title": "항상 다크 테마 사용",
"about_title": "PairDrop에 대하여",
"language-selector_title": "언어 설정",
"about_aria-label": "PairDrop에 대하여 보기",
"theme-light_title": "항상 라이트 테마 사용",
"theme-auto_title": "시스템에 맞춰 테마 자동 적용",
"join-public-room_title": "일시적으로 공개 방에 참여하기",
"cancel-share-mode": "취소",
"edit-share-mode": "편집",
"pair-device_title": "영구적으로 기기 연결하기",
"expand_title": "머리글 버튼 행 펼치기"
},
"instructions": {
"no-peers-subtitle": "장치를 연결하거나 공개 방에 들어가 다른 네트워크에서 검색 가능하게 하세요",
"no-peers_data-drop-bg": "해제하여 수신자 선택하기",
"x-instructions_mobile": "탭하여 파일을 보내거나 길게 탭하여 메시지를 보내세요",
"x-instructions_desktop": "클릭해 파일을 보내거나 오른쪽 클릭으로 메시지를 보내세요",
"no-peers-title": "다른 장치에서 PairDrop을 열어 파일 보내기",
"x-instructions_data-drop-bg": "해제하여 수신자 선택하기",
"x-instructions-share-mode_desktop": "클릭하여 {{descriptor}} 보내기",
"x-instructions-share-mode_mobile": "탭하여 {{descriptor}} 보내기",
"activate-share-mode-base": "다른 기기에서 PairDrop을 열어 보내기"
}
}
+10 -35
View File
@@ -51,10 +51,7 @@
"online-requirement-public-room": "U moet online zijn om een openbare kamer te maken", "online-requirement-public-room": "U moet online zijn om een openbare kamer te maken",
"copied-text-error": "Schrijven naar klembord mislukt. Kopieer handmatig!", "copied-text-error": "Schrijven naar klembord mislukt. Kopieer handmatig!",
"download-successful": "{{descriptor}} downloaden", "download-successful": "{{descriptor}} downloaden",
"click-to-show": "Klik om te tonen", "click-to-show": "Klik om te tonen"
"pair-url-copied-to-clipboard": "Koppeling om dit apparaat te koppelen is gekopieerd naar het klembord",
"room-url-copied-to-clipboard": "Koppeling naar openbare kamer gekopieerd naar klembord",
"notifications-permissions-error": "Meldingsmachtiging is geblokkeerd, omdat de gebruiker het machtigingsverzoek meerdere keren heeft afgewezen. Dit kan worden gereset in instellingen van de website, welke toegangelijk is door te klikken op het sloticoon naast de URL-balk."
}, },
"header": { "header": {
"cancel-share-mode": "Klaar", "cancel-share-mode": "Klaar",
@@ -68,15 +65,13 @@
"language-selector_title": "Taal Selecteren", "language-selector_title": "Taal Selecteren",
"about_title": "Over PairDrop", "about_title": "Over PairDrop",
"about_aria-label": "Open Over PairDrop", "about_aria-label": "Open Over PairDrop",
"theme-light_title": "Altijd lichte modus gebruiken", "theme-light_title": "Altijd lichte modus gebruiken"
"expand_title": "Knoppenrij uitvouwen",
"edit-share-mode": "Bewerken"
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen", "x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen",
"x-instructions-share-mode_desktop": "Klik om te verzenden {{descriptor}}", "x-instructions-share-mode_desktop": "Klik om te verzenden",
"activate-share-mode-and-other-files-plural": "en {{count}} andere bestanden", "activate-share-mode-and-other-files-plural": "en {{count}} andere bestanden",
"x-instructions-share-mode_mobile": "Tik om te verzenden {{descriptor}}", "x-instructions-share-mode_mobile": "Tik om te verzenden",
"activate-share-mode-base": "Open PairDrop op andere apparaten om te verzenden", "activate-share-mode-base": "Open PairDrop op andere apparaten om te verzenden",
"no-peers-subtitle": "Koppel apparaten of betreed een openbare ruimte om op andere netwerken zichtbaar te worden", "no-peers-subtitle": "Koppel apparaten of betreed een openbare ruimte om op andere netwerken zichtbaar te worden",
"activate-share-mode-shared-text": "gedeelde tekst", "activate-share-mode-shared-text": "gedeelde tekst",
@@ -84,11 +79,7 @@
"no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen", "no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen",
"x-instructions_data-drop-peer": "Laat los om naar peer te versturen", "x-instructions_data-drop-peer": "Laat los om naar peer te versturen",
"x-instructions_data-drop-bg": "Loslaten om ontvanger te selecteren", "x-instructions_data-drop-bg": "Loslaten om ontvanger te selecteren",
"no-peers_data-drop-bg": "Loslaten om ontvanger te kiezen", "no-peers_data-drop-bg": "Loslaten om ontvanger te kiezen"
"activate-share-mode-and-other-file": "en 1 ander bestand",
"activate-share-mode-shared-file": "gedeeld bestand",
"activate-share-mode-shared-files-plural": "{{count}} gedeelde bestanden",
"webrtc-requirement": "Om deze PairDrop instantie te kunnen gebruiken, moet WebRTC geactiveerd zijn!"
}, },
"peer-ui": { "peer-ui": {
"processing": "Verwerken…", "processing": "Verwerken…",
@@ -100,7 +91,7 @@
"transferring": "Overdragen…" "transferring": "Overdragen…"
}, },
"dialogs": { "dialogs": {
"base64-paste-to-send": "Plak klembord hier om {{type}} te versturen", "base64-paste-to-send": "Plak hier om {{type}} te versturen",
"auto-accept-instructions-2": "om automatisch alle bestanden van dat apparaat te accepteren.", "auto-accept-instructions-2": "om automatisch alle bestanden van dat apparaat te accepteren.",
"receive-text-title": "Bericht Ontvangen", "receive-text-title": "Bericht Ontvangen",
"edit-paired-devices-title": "Gekoppelde Apparaten Bewerken", "edit-paired-devices-title": "Gekoppelde Apparaten Bewerken",
@@ -116,7 +107,7 @@
"join": "Betreden", "join": "Betreden",
"title-image-plural": "Afbeeldingen", "title-image-plural": "Afbeeldingen",
"send": "Verzenden", "send": "Verzenden",
"base64-tap-to-paste": "Hier tikken om {{type}} te delen", "base64-tap-to-paste": "Hier tikken om {{type}} te plakken",
"base64-text": "tekst", "base64-text": "tekst",
"copy": "Kopiëren", "copy": "Kopiëren",
"file-other-description-image": "en één andere afbeelding", "file-other-description-image": "en één andere afbeelding",
@@ -130,7 +121,7 @@
"title-image": "Afbeelding", "title-image": "Afbeelding",
"file-other-description-file-plural": "en {{count}} andere bestanden", "file-other-description-file-plural": "en {{count}} andere bestanden",
"would-like-to-share": "zou graag het volgende willen delen", "would-like-to-share": "zou graag het volgende willen delen",
"send-message-to": "Aan:", "send-message-to": "Verstuur een bericht naar",
"language-selector-title": "Taal Instellen", "language-selector-title": "Taal Instellen",
"pair": "Koppel", "pair": "Koppel",
"hr-or": "OF", "hr-or": "OF",
@@ -146,19 +137,7 @@
"send-message-title": "Bericht Sturen", "send-message-title": "Bericht Sturen",
"input-room-id-on-another-device": "Voer de kamer ID in op een ander apparaat", "input-room-id-on-another-device": "Voer de kamer ID in op een ander apparaat",
"file-other-description-image-plural": "en {{count}} andere afbeeldingen", "file-other-description-image-plural": "en {{count}} andere afbeeldingen",
"enter-room-id-from-another-device": "Voer de kamer ID van een ander apparaat hier in.", "enter-room-id-from-another-device": "Voer de kamer ID van een ander apparaat hier in."
"paired-device-removed": "Gekoppelde apparaten zijn verwijderd.",
"message_title": "Voer bericht in om te versturen",
"message_placeholder": "Tekst",
"base64-title-files": "Deel Bestanden",
"base64-title-text": "Deel Tekst",
"public-room-qr-code_title": "Klik, om de link naar deze openbare ruimte te kopiëren",
"approve": "goedkeuren",
"share-text-title": "Gedeeld Tekst Bericht",
"share-text-checkbox": "Deze dialoog altijd tonen bij het delen van tekst",
"close-toast_title": "Bericht sluiten",
"pair-devices-qr-code_title": "Klik om de koppeling te kopiëren om dit apparaat te koppelen",
"share-text-subtitle": "Bewerk bericht voor verzending:"
}, },
"about": { "about": {
"claim": "De makkelijkste manier om bestanden tussen apparaten te versturen", "claim": "De makkelijkste manier om bestanden tussen apparaten te versturen",
@@ -166,11 +145,7 @@
"close-about_aria-label": "Sluit Over PairDrop", "close-about_aria-label": "Sluit Over PairDrop",
"buy-me-a-coffee_title": "Koop een kopje koffie voor mij!", "buy-me-a-coffee_title": "Koop een kopje koffie voor mij!",
"github_title": "PairDrop op GitHub", "github_title": "PairDrop op GitHub",
"faq_title": "Veel gestelde vragen", "faq_title": "Veel gestelde vragen"
"mastodon_title": "Schrijf over PairDrop op Mastodon",
"bluesky_title": "Volg ons op BlueSky",
"custom_title": "Volg ons",
"privacypolicy_title": "Open ons privacybeleid"
}, },
"document-titles": { "document-titles": {
"file-transfer-requested": "Bestandsoverdracht verzocht", "file-transfer-requested": "Bestandsoverdracht verzocht",
-184
View File
@@ -1,184 +0,0 @@
{
"header": {
"language-selector_title": "Ustaw język",
"about_aria-label": "Otwórz \"O PairDrop\"",
"theme-light_title": "Zawsze używaj jasnego motywu",
"theme-dark_title": "Zawsze używaj ciemnego motywu",
"pair-device_title": "Sparuj swoje urządzenia na stałe",
"edit-paired-devices_title": "Edytuj sparowane urządzenia",
"join-public-room_title": "Dołącz tymczasowo do pokoju publicznego",
"cancel-share-mode": "Anuluj",
"edit-share-mode": "Edytuj",
"expand_title": "Rozwiń rząd przycisków",
"about_title": "O PairDrop",
"theme-auto_title": "Dostosuj motyw do motywu systemowego",
"notification_title": "Włącz powiadomienia",
"install_title": "Zainstaluj PairDrop"
},
"instructions": {
"no-peers_data-drop-bg": "Zwolnij, aby wybrać odbiorcę",
"no-peers-title": "Otwórz PairDrop na innych urządzeniach, aby wysłać pliki",
"x-instructions_desktop": "Kliknij, aby wysłać pliki lub kliknij prawym przyciskiem myszy, aby wysłać wiadomość",
"x-instructions_mobile": "Naciśnij, aby wysłać pliki lub naciśnij i przytrzymaj, aby wysłać wiadomość",
"x-instructions_data-drop-bg": "Zwolnij, aby wybrać odbiorcę",
"x-instructions-share-mode_desktop": "Kliknij, aby wysłać {{descriptor}}",
"x-instructions-share-mode_mobile": "Naciśnij, aby wysłać {{descriptor}}",
"activate-share-mode-base": "Otwórz PairDrop na innych urządzeniach, aby wysłać",
"activate-share-mode-and-other-file": "i 1 inny plik",
"activate-share-mode-shared-text": "udostępniony tekst",
"activate-share-mode-shared-file": "udostępniony plik",
"no-peers-subtitle": "Sparuj urządzenia lub wejdź do pokoju publicznego, aby być widocznym w innych sieciach",
"x-instructions_data-drop-peer": "Zwolnij, aby wysłać do odbiorcy",
"activate-share-mode-and-other-files-plural": "i {{count}} innych plików",
"activate-share-mode-shared-files-plural": "{{count}} udostępnionych plików",
"webrtc-requirement": "Aby móc korzystać z tej instancji PairDrop, należy włączyć WebRTC!"
},
"footer": {
"known-as": "Jesteś widoczny jako:",
"display-name_data-placeholder": "Ładowanie…",
"display-name_title": "Edytuj nazwę swojego urządzenia na stałe",
"on-this-network": "w tej sieci",
"on-this-network_title": "Możesz być znaleziony przez każdego w tej sieci.",
"paired-devices": "przez sparowane urządzenia",
"discovery": "Możesz być wykryty:",
"paired-devices_title": "Możesz zostać wykryty przez sparowane urządzenia przez cały czas, niezależnie od sieci.",
"public-room-devices": "w pokoju {{roomId}}",
"public-room-devices_title": "Możesz zostać wykryty przez urządzenia w tym pokoju publicznym, niezależnie od sieci.",
"routed": "przesyłane przez serwer",
"webrtc": "jeśli WebRTC jest niedostępny.",
"traffic": "Transmisja jest"
},
"dialogs": {
"pair-devices-title": "Sparuj Urządzenia Na Stałe",
"enter-key-from-another-device": "Wpisz tu klucz z innego urządzenia.",
"temporary-public-room-title": "Tymczasowy Pokój Publiczny",
"enter-room-id-from-another-device": "Wprowadź ID pokoju z innego urządzenia, aby wejść do pokoju.",
"hr-or": "LUB",
"pair": "Sparuj",
"cancel": "Anuluj",
"edit-paired-devices-title": "Edycja Sparowanych Urządzeń",
"unpair": "Rozłącz sparowanie",
"paired-devices-wrapper_data-empty": "Brak sparowanych urządzeń.",
"auto-accept-instructions-1": "Aktywuj",
"auto-accept-instructions-2": ", aby automatycznie przyjąć wszystkie pliki wysłane z tego urządzenia.",
"auto-accept": "samoczynne potwierdzanie",
"close": "Zamknij",
"join": "Dołącz",
"leave": "Opuść",
"accept": "Akceptuj",
"decline": "Odrzuć",
"has-sent": "wysłał:",
"share": "Udostępnij",
"send-message-title": "Wyślij Wiadomość",
"send-message-to": "Do:",
"message_title": "Wprowadź wiadomość do wysłania",
"message_placeholder": "Tekst",
"receive-text-title": "Wiadomość Odebrana",
"copy": "Kopiuj",
"base64-title-files": "Udostępnij Pliki",
"base64-title-text": "Udostępnij Tekst",
"base64-processing": "Przetwarzanie…",
"base64-tap-to-paste": "Naciśnij tutaj, aby udostępnić {{type}}",
"base64-text": "tekst",
"base64-files": "pliki",
"file-other-description-image": "i 1 inny obraz",
"file-other-description-image-plural": "i {{count}} innych obrazów",
"file-other-description-file-plural": "i {{count}} innych plików",
"title-image": "Obraz",
"title-image-plural": "Obrazy",
"title-file-plural": "Pliki",
"receive-title": "{{descriptor}} Otrzymano",
"download-again": "Pobierz ponownie",
"download": "Pobierz",
"language-selector-title": "Ustaw Język",
"system-language": "Język Systemu",
"file-other-description-file": "i 1 inny plik",
"pair-devices-qr-code_title": "Kliknij, aby skopiować odnośnik do sparowania urządzenia",
"approve": "zatwierdź",
"share-text-subtitle": "Edytuj wiadomość przed wysłaniem:",
"share-text-checkbox": "Zawsze pokazuj to okno, gdy udostępniasz tekst",
"paired-device-removed": "Sparowane urządzenie zostało usunięte.",
"would-like-to-share": "chciałby udostępnić",
"input-key-on-this-device": "Wprowadź ten klucz na innym urządzeniu",
"send": "Wyślij",
"scan-qr-code": "lub zeskanuj kod QR.",
"base64-paste-to-send": "Wklej tutaj, aby udostępnić {{type}}",
"title-file": "Plik",
"input-room-id-on-another-device": "Wprowadź ID tego pokoju na innym urządzeniu",
"public-room-qr-code_title": "Kliknij, aby skopiować odnośnik do pokoju publicznego",
"share-text-title": "Udostępnij Wiadomość Tekstową",
"close-toast_title": "Zamknij powiadomienie"
},
"about": {
"close-about_aria-label": "Zamknij \"O PairDrop\"",
"claim": "Najłatwiejszy sposób na przesyłanie plików między urządzeniami",
"buy-me-a-coffee_title": "Kup me kawę!",
"mastodon_title": "Napisz o PairDrop na Mastodonie",
"bluesky_title": "Śledź nas na BlueSky",
"custom_title": "Śledź nas",
"privacypolicy_title": "Otwórz naszą Politykę Prywatności",
"faq_title": "Często zadawane pytania (FAQ)",
"github_title": "PairDrop na GitHub",
"tweet_title": "Tweetnij o PairDrop"
},
"notifications": {
"display-name-changed-permanently": "Wyświetlana nazwa została zmieniona na stałe",
"display-name-random-again": "Wyświetlana nazwa została ponownie wygenerowana",
"download-successful": "{{descriptor}} pobrano",
"pairing-tabs-error": "Sparowanie dwóch kart w przeglądarce jest niemożliwe",
"pairing-success": "Sparowane urządzenia",
"pairing-key-invalid": "Nieprawidłowy klucz",
"pairing-key-invalidated": "Klucz {{key}} unieważniony",
"public-room-id-invalid": "Nieprawidłowe ID pokoju",
"pairing-cleared": "Wszystkie urządzenia rozłączone",
"public-room-left": "Opuściłeś pokój publiczny {{publicRoomId}}",
"copied-to-clipboard-error": "Kopiowanie niemożliwe. Skopiuj ręcznie.",
"text-content-incorrect": "Treść tekstu jest nieprawidłowa",
"file-content-incorrect": "Zawartość pliku jest nieprawidłowa",
"clipboard-content-incorrect": "Zawartość schowka jest nieprawidłowa",
"pair-url-copied-to-clipboard": "Link do sparowania tego urządzenia skopiowano do schowka",
"link-received": "Otrzymano link od {{name}} - Kliknij, aby otworzyć",
"message-received": "Otrzymano wiadomość od {{name}} - Kliknij, aby skopiować",
"click-to-download": "Kliknij, aby pobrać",
"request-title": "{{name}} chciałby przesłać {{count}} {{descriptor}}",
"click-to-show": "Kliknij, żeby pokazać",
"copied-text": "Tekst skopiowany do schowka",
"copied-text-error": "Zapis do schowka nie powiódł się. Skopiuj ręcznie!",
"offline": "Jesteś offline",
"online": "Jesteś znów online",
"connected": "Połączony",
"online-requirement-pairing": "Aby sparować urządzenia, musisz być online",
"online-requirement-public-room": "Aby utworzyć pokój publiczny, musisz być online",
"connecting": "Łączenie…",
"files-incorrect": "Pliki są nieprawidłowe",
"file-transfer-completed": "Przesyłanie plików zakończone",
"ios-memory-limit": "Jednorazowe przesyłanie plików do iOS jest możliwe tylko do 200 MB",
"message-transfer-completed": "Przesyłanie wiadomości zakończone",
"unfinished-transfers-warning": "Istnieją niedokończone transfery. Czy na pewno chcesz zamknąć PairDrop?",
"rate-limit-join-key": "Osiągnięto limit. Poczekaj 10 sekund i spróbuj ponownie.",
"selected-peer-left": "Wybrany uczestnik opuścił",
"display-name-changed-temporarily": "Wyświetlana nazwa została zmieniona na czas tej sesji",
"pairing-not-persistent": "Połączenie sparowanych urządzeń nie jest trwałe",
"copied-to-clipboard": "Skopiowano do schowka",
"room-url-copied-to-clipboard": "Link do publicznego pokoju skopiowano do schowka",
"notifications-enabled": "Powiadomienia włączone",
"notifications-permissions-error": "Uprawnienia do powiadomień zostały zablokowane, ponieważ użytkownik kilkakrotnie odrzucił monit o pozwolenie na nie. Można je zresetować w \"Informacjach o [stronie]\", do których można uzyskać dostęp, klikając ikonę kłódki obok paska adresu URL."
},
"document-titles": {
"file-received": "Plik Odebrany",
"file-received-plural": "{{count}} Plików Odebranych",
"file-transfer-requested": "Zażądano Przesłania Pliku",
"image-transfer-requested": "Zażądano Przesłania Obrazu",
"message-received": "Otrzymano Wiadomość",
"message-received-plural": "{{count}} Wiadomości Odebranych"
},
"peer-ui": {
"click-to-send-share-mode": "Kliknij, aby wysłać {{descriptor}}",
"click-to-send": "Kliknij, aby wysłać pliki lub kliknij prawym przyciskiem myszy, aby wysłać wiadomość",
"connection-hash": "Aby sprawdzić bezpieczeństwo szyfrowania end-to-end, porównaj ten numer bezpieczeństwa na obu urządzeniach",
"preparing": "Przygotowanie…",
"waiting": "Oczekiwanie…",
"processing": "Przetwarzanie…",
"transferring": "Przesyłanie…"
}
}
+3 -3
View File
@@ -11,7 +11,7 @@
"pair-device_title": "Emparelhar seus dispositivos permanentemente", "pair-device_title": "Emparelhar seus dispositivos permanentemente",
"edit-paired-devices_title": "Editar dispositivos emparelhados", "edit-paired-devices_title": "Editar dispositivos emparelhados",
"join-public-room_title": "Entrar em uma sala pública temporariamente", "join-public-room_title": "Entrar em uma sala pública temporariamente",
"cancel-share-mode": "Cancelar", "cancel-share-mode": "Concluído",
"edit-share-mode": "Editar" "edit-share-mode": "Editar"
}, },
"instructions": { "instructions": {
@@ -22,8 +22,8 @@
"x-instructions_mobile": "Toque para enviar arquivos ou toque e segure para enviar uma mensagem", "x-instructions_mobile": "Toque para enviar arquivos ou toque e segure para enviar uma mensagem",
"x-instructions_data-drop-peer": "Solte para enviar para o par", "x-instructions_data-drop-peer": "Solte para enviar para o par",
"x-instructions_data-drop-bg": "Solte para selecionar o destinatário", "x-instructions_data-drop-bg": "Solte para selecionar o destinatário",
"x-instructions-share-mode_desktop": "Clique para enviar {{descriptor}}", "x-instructions-share-mode_desktop": "Clique para enviar",
"x-instructions-share-mode_mobile": "Toque para enviar {{descriptor}}", "x-instructions-share-mode_mobile": "Toque para enviar",
"activate-share-mode-base": "Abra o PairDrop em outros dispositivos para enviar", "activate-share-mode-base": "Abra o PairDrop em outros dispositivos para enviar",
"activate-share-mode-and-other-files-plural": "e {{count}} outros arquivos", "activate-share-mode-and-other-files-plural": "e {{count}} outros arquivos",
"activate-share-mode-shared-text": "texto compartilhado", "activate-share-mode-shared-text": "texto compartilhado",
+4 -7
View File
@@ -11,15 +11,14 @@
"theme-dark_title": "Всегда использовать темную тему", "theme-dark_title": "Всегда использовать темную тему",
"theme-light_title": "Всегда использовать светлую тему", "theme-light_title": "Всегда использовать светлую тему",
"join-public-room_title": "Войти на время в публичную комнату", "join-public-room_title": "Войти на время в публичную комнату",
"language-selector_title": "Выбрать язык", "language-selector_title": "Выбрать язык"
"edit-share-mode": "Редактировать"
}, },
"instructions": { "instructions": {
"x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение", "x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение",
"no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя", "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя",
"x-instructions-share-mode_desktop": "Нажмите, чтобы отправить {{descriptor}}", "x-instructions-share-mode_desktop": "Нажмите, чтобы отправить",
"x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя", "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя",
"x-instructions-share-mode_mobile": "Прикоснитесь, чтобы отправить {{descriptor}}", "x-instructions-share-mode_mobile": "Прикоснитесь, чтобы отправить",
"x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу", "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу",
"x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение", "x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение",
"no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы", "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы",
@@ -93,9 +92,7 @@
"temporary-public-room-title": "Временная публичная комната", "temporary-public-room-title": "Временная публичная комната",
"message_title": "Вставьте сообщение для отправки", "message_title": "Вставьте сообщение для отправки",
"pair-devices-qr-code_title": "Нажмите, чтобы скопировать ссылку для привязки этого устройства", "pair-devices-qr-code_title": "Нажмите, чтобы скопировать ссылку для привязки этого устройства",
"public-room-qr-code_title": "Нажмите, чтобы скопировать ссылку на публичную комнату", "public-room-qr-code_title": "Нажмите, чтобы скопировать ссылку на публичную комнату"
"message_placeholder": "Текст",
"paired-device-removed": "Связанное устройство удалено."
}, },
"about": { "about": {
"close-about-aria-label": "Закрыть страницу \"О сервисе\"", "close-about-aria-label": "Закрыть страницу \"О сервисе\"",
+94 -95
View File
@@ -1,163 +1,162 @@
{ {
"header": { "header": {
"about_title": "PairDrop Hakkında", "about_title": "PairDrop Hakkında",
"about_aria-label": "PairDrop Hakkında'yı Aç", "about_aria-label": "PairDrop Hakkındayı Aç",
"theme-auto_title": "Temayı sisteme otomatik olarak adapte et", "theme-auto_title": "Temayı sisteme uyarla",
"theme-light_title": "Her zaman açık temayı kullan", "theme-light_title": "Daima açık tema kullan",
"theme-dark_title": "Her zaman koyu temayı kullan", "theme-dark_title": "Daima koyu tema kullan",
"notification_title": "Bildirimleri etkinleştir", "notification_title": "Bildirimleri etkinleştir",
"install_title": "PairDrop'u Yükle", "install_title": "PairDrop'u Yükle",
"pair-device_title": "Cihazlarınızı kalıcı olarak eşleştirin", "pair-device_title": "Cihazı kalıcı olarak eşle",
"edit-paired-devices_title": "Eşleşmiş cihazları düzenle", "edit-paired-devices_title": "Eşleştirilmiş cihazları düzenle",
"cancel-share-mode": "İptal Et", "cancel-share-mode": "Bitti",
"join-public-room_title": "Geçici olarak ortak odaya katıl", "join-public-room_title": "Geçici olarak genel odaya katılın",
"language-selector_title": "Dili Ayarla", "language-selector_title": "Dili Seç",
"edit-share-mode": "Düzenle", "edit-share-mode": "Düzenle",
"expand_title": "Başlık buton satırını genişlet" "expand_title": "Başlık düğmesi satırını genişlet"
}, },
"instructions": { "instructions": {
"no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın", "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın",
"x-instructions_mobile": "Dosya göndermek için dokunun veya mesaj göndermek için uzun dokunun", "x-instructions_mobile": "Dosya göndermek için dokun veya mesaj göndermek için uzun dokun",
"x-instructions-share-mode_desktop": "{{descriptor}} göndermek için tıklayın", "x-instructions-share-mode_desktop": "{{descriptor}} kişisine göndermek için tıkla",
"activate-share-mode-and-other-files-plural": "ve {{count}} diğer dosya", "activate-share-mode-and-other-files-plural": "ve {{count}} diğer dosya",
"x-instructions-share-mode_mobile": "{{descriptor}} göndermek için dokunun", "x-instructions-share-mode_mobile": "{{descriptor}} kişisine göndermek için dokun",
"activate-share-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın", "activate-share-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın",
"no-peers-subtitle": "Cihazları eşleştirin veya keşfedilebilir olmak için ortak bir odaya girin", "no-peers-subtitle": "Diğer ağlarda keşfedilebilir olmak için cihazları eşleştirin veya ortak bir odaya girin",
"activate-share-mode-shared-text": "paylaşılan metin", "activate-share-mode-shared-text": "paylaşılan metin",
"x-instructions_desktop": "Dosya göndermek için tıklayın veya mesaj göndermek için sağ tıklayın", "x-instructions_desktop": "Dosya göndermek için tıkla veya mesaj göndermek için sağ tıkla",
"no-peers-title": "Dosya göndermek için diğer cihazlarda PairDrop'u açın", "no-peers-title": "Dosya göndermek için diğer cihazlarda PairDrop'u açın",
"x-instructions_data-drop-peer": "Eşleştiriciye göndermek için bırakın", "x-instructions_data-drop-peer": "Göndermek için serbest bırak",
"x-instructions_data-drop-bg": "Alıcıyı seçmek için bırakın", "x-instructions_data-drop-bg": "Alıcıyı seçmek için bırakın",
"webrtc-requirement": "Bu PairDrop örneğini kullanmak için WebRTC etkin olmalı!", "webrtc-requirement": "Bu PairDrop örneğini kullanmak için WebRTC etkinleştirilmelidir!",
"activate-share-mode-shared-files-plural": "{{count}} paylaşılan dosya", "activate-share-mode-shared-files-plural": "{{count}} adet paylaşılan dosya",
"activate-share-mode-shared-file": "paylaşılan dosya", "activate-share-mode-shared-file": "paylaşılan dosya",
"activate-share-mode-and-other-file": "ve 1 diğer dosya" "activate-share-mode-and-other-file": "ve 1 dosya"
}, },
"footer": { "footer": {
"display-name_data-placeholder": "Yükleniyor…", "display-name_data-placeholder": "Yükleniyor…",
"display-name_title": "Cihaz adınızı kalıcı olarak düzenleyin", "display-name_title": "Cihazının adını kalıcı olarak düzenle",
"webrtc": "WebRTC mevcut değilse.", "webrtc": "WebRTC mevcut değilse.",
"public-room-devices_title": "Ağdan bağımsız olarak bu ortak odadaki cihazlar tarafından keşfedilebilir olabilirsiniz.", "public-room-devices_title": "Genel odada ağdan bağımsız olarak cihazlar tarafından keşfedilebilirsiniz.",
"traffic": "Trafik", "traffic": "Trafik",
"paired-devices_title": "Ağdan bağımsız olarak her zaman eşleştirilmiş cihazlar tarafından keşfedilebilir olabilirsiniz.", "paired-devices_title": "Eşleştirilmiş cihazlarda ağdan bağımsız olarak her zaman keşfedilebilirsiniz.",
"public-room-devices": "{{roomId}} odasında", "public-room-devices": "{{roomId}} odası",
"paired-devices": "eşleştirilmiş cihazlar tarafından", "paired-devices": "eşleştirilmiş cihazlar tarafından",
"on-this-network": "bu ağda", "on-this-network": "bu ağ üzerinde",
"routed": "sunucu üzerinden yönlendirilmiş", "routed": "sunucu üzerinden yönlendirilir",
"discovery": "Keşfedilebilir durumdasınız:", "discovery": "Keşfedilebilirsin:",
"on-this-network_title": "Bu ağdaki herkes tarafından keşfedilebilir olabilirsiniz.", "on-this-network_title": "Bu ağdaki herkes tarafından keşfedilebilirsiniz.",
"known-as": "Şu adla biliniyorsunuz:" "known-as": "Bilinen adın:"
}, },
"dialogs": { "dialogs": {
"cancel": "İptal", "cancel": "İptal",
"edit-paired-devices-title": "Eşleşmiş Cihazları Düzenle", "edit-paired-devices-title": "Eşleştirilmiş Cihazları Düzenle",
"base64-paste-to-send": "{{type}} paylaşmak için buraya yapıştırın", "base64-paste-to-send": "{{type}} paylaşmak için buraya yapıştır",
"auto-accept-instructions-2": "bu cihazdan gönderilen tüm dosyaları otomatik olarak kabul etmek için.", "auto-accept-instructions-2": "böylelikle cihazdan gönderilen tüm dosyaları otomatik olarak kabul eder.",
"receive-text-title": "Mesaj Alındı", "receive-text-title": "Mesaj Alındı",
"auto-accept-instructions-1": "Aktive et", "auto-accept-instructions-1": "Etkinleştir",
"pair-devices-title": "Cihazları Kalıcı Olarak Eşleştir", "pair-devices-title": "Cihazları Kalıcı Eşleştir",
"download": "İndir", "download": "İndir",
"title-file": "Dosya", "title-file": "Dosya",
"base64-processing": "İşleniyor…", "base64-processing": "İşleniyor…",
"decline": "Reddet", "decline": "Reddet",
"receive-title": "{{descriptor}} Alındı", "receive-title": "{{descriptor}} Alındı",
"leave": "Ayrıl", "leave": "Ayrıl",
"message_title": "Göndermek için mesaj ekle", "message_title": "Göndermek için mesaj girin",
"join": "Katıl", "join": "Katıl",
"title-image-plural": "Resimler", "title-image-plural": "Resimler",
"send": "Gönder", "send": "Gönder",
"base64-tap-to-paste": "{{type}} paylaşmak için buraya dokunun", "base64-tap-to-paste": "{{type}} paylaşmak için buraya dokun",
"base64-text": "metin", "base64-text": "metin",
"copy": "Kopyala", "copy": "Kopyala",
"file-other-description-image": "ve 1 diğer resim", "file-other-description-image": "ve 1 diğer resim",
"pair-devices-qr-code_title": "Bu cihazı eşleştirmek için bağlantıyı kopyalamak için tıklayın", "pair-devices-qr-code_title": "Bu cihazı eşleştirmek üzere bağlantıyı kopyalamak için tıklayın",
"temporary-public-room-title": "Geçici Ortak Oda", "temporary-public-room-title": "Geçici Genel Oda",
"base64-files": "dosyalar", "base64-files": "dosyalar",
"has-sent": "gönderdi:", "has-sent": "gönderdi:",
"file-other-description-file": "ve 1 diğer dosya", "file-other-description-file": "ve 1 diğer dosya",
"public-room-qr-code_title": "Ortak oda bağlantısını kopyalamak için tıklayın", "public-room-qr-code_title": "Genel odanın bağlantı linkini kopyalamak için tıkla",
"close": "Kapat", "close": "Kapat",
"system-language": "Sistem Dili", "system-language": "Sistem Dili",
"unpair": "Eşleşmeyi Kaldır", "unpair": "Eşlemeyi Kaldır",
"title-image": "Resim", "title-image": "Resim",
"file-other-description-file-plural": "ve {{count}} diğer dosya", "file-other-description-file-plural": "ve {{count}} diğer dosya",
"would-like-to-share": "paylaşmak istiyor", "would-like-to-share": "paylaşmak istiyor",
"send-message-to": "Alıcı:", "send-message-to": "Kime:",
"language-selector-title": "Dili Ayarla", "language-selector-title": "Dili Seç",
"pair": "Eşleştir", "pair": "Eşle",
"hr-or": "VEYA", "hr-or": "VEYA",
"scan-qr-code": "veya QR kodunu tarayın.", "scan-qr-code": "veya QR kodunu tarayın.",
"input-key-on-this-device": "Bu anahtarı başka bir cihaza girin", "input-key-on-this-device": "Bu anahtarı başka bir cihazda girin",
"download-again": "Tekrar indir", "download-again": "Tekrar indir",
"accept": "Kabul Et", "accept": "Kabul Et",
"paired-devices-wrapper_data-empty": "Eşleşmiş cihaz yok.", "paired-devices-wrapper_data-empty": "Eşlenmiş cihaz yok.",
"enter-key-from-another-device": "Başka bir cihazdan alınan anahtarı buraya girin.", "enter-key-from-another-device": "Buraya başka bir cihazdan anahtar girin.",
"share": "Paylaş", "share": "Paylaş",
"auto-accept": "otomatik kabul", "auto-accept": "otomatik-kabul",
"title-file-plural": "Dosyalar", "title-file-plural": "Dosyalar",
"send-message-title": "Mesaj Gönder", "send-message-title": "Mesaj Gönder",
"input-room-id-on-another-device": "Bu oda kimliğini başka bir cihaza girin", "input-room-id-on-another-device": "Bu ID'yi diğer cihaza girin",
"file-other-description-image-plural": "ve {{count}} diğer resim", "file-other-description-image-plural": "ve {{count}} diğer resim",
"enter-room-id-from-another-device": "Odaya katılmak için başka bir cihazdan oda kimliğini girin.", "enter-room-id-from-another-device": "Odaya katılmak için diğer cihazın ID'sini girin.",
"message_placeholder": "Metin", "message_placeholder": "Metin",
"close-toast_title": "Bildirim kapat", "close-toast_title": "Bildirimleri kapat",
"share-text-checkbox": "Metin paylaşırken her zaman bu iletişim kutusunu göster", "share-text-checkbox": "Metin paylaşırken her zaman bu pencereyi göster",
"base64-title-files": "Dosyaları Paylaş", "base64-title-files": "Dosyaları Paylaş",
"approve": "onayla", "approve": "onayla",
"paired-device-removed": "Eşleşmiş cihaz kaldırıldı.", "paired-device-removed": "Eşleştirilmiş cihaz kaldırıldı.",
"share-text-title": "Metin Mesajı Paylaş", "share-text-title": "Kısa Mesaj Paylaş",
"share-text-subtitle": "Göndermeden önce mesajı düzenleyin:", "share-text-subtitle": "Göndermeden önce mesajı düzenle:",
"base64-title-text": "Metni Paylaş" "base64-title-text": "Metin Paylaş"
}, },
"notifications": { "notifications": {
"request-title": "{{name}} {{count}} {{descriptor}} transfer etmek istiyor", "request-title": "{{name}} {{count}} {{descriptor}} transfer etmek istiyor",
"unfinished-transfers-warning": "Tamamlanmamış transferler var. PairDrop'u kapatmak istediğinizden emin misiniz?", "unfinished-transfers-warning": "Bitmemiş transferler var. PairDrop'u kapatmak istediğinize emin misiniz?",
"message-received": "{{name}} tarafından mesaj alındı - Kopyalamak için tıklayın", "message-received": "Mesaj {{name}} tarafından alındı - Kopyalamak için tıkla",
"notifications-permissions-error": "Bildirim izinleri birkaç kez reddedildiği için engellendi. Bu, URL çubuğunun yanındaki kilit simgesine tıklayarak erişilebilen Sayfa Bilgilerinde sıfırlanabilir.", "notifications-permissions-error": "Kullanıcı izin isteğini birkaç kez reddettiği için bildirimler engellenmiştir. URL çubuğunun yanındaki kilit simgesine tıklayarak sıfırlanabilir.",
"rate-limit-join-key": "Hız sınırına ulaşıldı. 10 saniye bekleyin ve tekrar deneyin.", "rate-limit-join-key": "İstek sınırına ulaşıldı. 10 saniye bekleyin ve tekrar deneyin.",
"pair-url-copied-to-clipboard": "Bu cihazı eşleştirmek için bağlantı panoya kopyalandı", "pair-url-copied-to-clipboard": "Bu cihazı eşleştirmek için bağlantı linki panoya kopyalandı",
"connecting": "Bağlanıyor…", "connecting": "Bağlanılıyor…",
"pairing-key-invalidated": "Anahtar {{key}} geçersiz kılındı", "pairing-key-invalidated": "{{key}} anahtarı geçersiz",
"pairing-key-invalid": "Geçersiz anahtar", "pairing-key-invalid": "Geçersiz anahtar",
"connected": "Bağlı", "connected": "Bağlandı",
"pairing-not-persistent": "Eşleşmiş cihazlar kalıcı değil", "pairing-not-persistent": "Eşleştirilmiş cihazlar kalıcı değildir",
"text-content-incorrect": "Metin içeriği hatalı", "text-content-incorrect": "Metin içeriği yanlış",
"message-transfer-completed": "Mesaj transferi tamamlandı", "message-transfer-completed": "Mesaj transferi tamamlandı",
"file-transfer-completed": "Dosya transferi tamamlandı", "file-transfer-completed": "Dosya transferi bitti",
"file-content-incorrect": "Dosya içeriği hatalı", "file-content-incorrect": "Dosya içeriği yanlış",
"files-incorrect": "Dosyalar hatalı", "files-incorrect": "Dosyalar yanlış",
"selected-peer-left": "Seçilen eş ayrıldı", "selected-peer-left": "Seçili aygıt ayrıldı",
"link-received": "{{name}} tarafından bağlantı alındı - Açmak için tıklayın", "link-received": "Link {{name}} tarafından alındı - Açmak için tıkla",
"online": "Tekrar çevrimiçisiniz", "online": "Tekrar çevrimiçisin",
"public-room-left": "Ortak odadan ayrıldı {{publicRoomId}}", "public-room-left": "{{publicRoomId}} genel odasından ayrıldın",
"copied-text": "Metin panoya kopyalandı", "copied-text": "Metin panoya kopyalandı",
"display-name-random-again": "Görünen ad tekrar rastgele oluşturuldu", "display-name-random-again": "Mevcut adın tekrardan rastgele oluşturuldu",
"display-name-changed-permanently": "Görünen ad kalıcı olarak değiştirildi", "display-name-changed-permanently": "Mevcut adın kalıcı olarak değiştirildi",
"copied-to-clipboard-error": "Kopyalama mümkün değil. Elle kopyalayın.", "copied-to-clipboard-error": "Kopyalama mümkün değil. Manuel olarak kopyalayın.",
"pairing-success": "Cihazlar eşleştirildi", "pairing-success": "Cihazlar eşleştirildi",
"clipboard-content-incorrect": "Panodaki içerik hatalı", "clipboard-content-incorrect": "Pano içeriği yanlış",
"display-name-changed-temporarily": "Görünen ad sadece bu oturum için değiştirildi", "display-name-changed-temporarily": "Mevcut adın yalnızca bu oturum için değiştirildi",
"copied-to-clipboard": "Panoya kopyalandı", "copied-to-clipboard": "Panoya kopyalandı",
"offline": "Çevrimdışısınız", "offline": "Çevrimdışısın",
"pairing-tabs-error": "İki web tarayıcı sekmesini eşleştirmek imkansız", "pairing-tabs-error": "İki web tarayıcı sekmesini eşleştirmek mümkün değildir",
"public-room-id-invalid": "Geçersiz oda kimliği", "public-room-id-invalid": "Geçersiz oda ID'si",
"click-to-download": "İndirmek için tıklayın", "click-to-download": "İndirmek için tıkla",
"pairing-cleared": "Tüm cihazların eşleşmesi kaldırıldı", "pairing-cleared": "Tüm cihazlar eşleştirmeden çıkarıldı",
"notifications-enabled": "Bildirimler etkinleştirildi", "notifications-enabled": "Bildirimler etkinleştirildi",
"online-requirement-pairing": "Cihazları eşleştirmek için çevrimiçi olmalısınız", "online-requirement-pairing": "Cihazları eşleştirmek için çevrimiçi olmanız lazım",
"ios-memory-limit": "iOS'a dosya göndermek sadece bir seferde 200 MB'a kadar mümkündür", "online-requirement-public-room": "Genel oda oluşturmak için çevrimiçi olmanız lazım",
"online-requirement-public-room": "Ortak oda oluşturmak için çevrimiçi olmalısınız", "room-url-copied-to-clipboard": "Genel oda bağlantı linki panoya kopyalandı",
"room-url-copied-to-clipboard": "Ortak oda bağlantısı panoya kopyalandı", "copied-text-error": "Panoya kopyalanamadı. Lütfen manuel olarak kopyalayın!",
"copied-text-error": "Panoya yazma başarısız oldu. Elle kopyalayın!",
"download-successful": "{{descriptor}} indirildi", "download-successful": "{{descriptor}} indirildi",
"click-to-show": "Göstermek için tıklayın" "click-to-show": "Görmek için tıkla"
}, },
"peer-ui": { "peer-ui": {
"processing": "İşleniyor…", "processing": "İşleniyor…",
"click-to-send-share-mode": "{{descriptor}} göndermek için tıklayın", "click-to-send-share-mode": "{{descriptor}} göndermek için tıkla",
"click-to-send": "Dosya göndermek için tıklayın veya mesaj göndermek için sağ tıklayın", "click-to-send": "Dosya göndermek için tıkla veya mesaj göndermek için sağ tıkla",
"waiting": "Bekleniyor…", "waiting": "Bekleniyor…",
"connection-hash": "Uçtan uca şifrelemenin güvenliğini doğrulamak için, bu güvenlik numarasını her iki cihazda da karşılaştırın", "connection-hash": "Uçtan uca şifrelemenin güvenliğini doğrulamak için her iki cihazda da bu güvenlik numarasını karşılaştırın",
"preparing": "Hazırlanıyor…", "preparing": "Hazırlanıyor…",
"transferring": "Transfer ediliyor…" "transferring": "Transfer ediliyor…"
}, },
@@ -165,17 +164,17 @@
"claim": "Cihazlar arasında dosya aktarmanın en kolay yolu", "claim": "Cihazlar arasında dosya aktarmanın en kolay yolu",
"tweet_title": "PairDrop hakkında tweet at", "tweet_title": "PairDrop hakkında tweet at",
"close-about_aria-label": "PairDrop Hakkında'yı Kapat", "close-about_aria-label": "PairDrop Hakkında'yı Kapat",
"buy-me-a-coffee_title": "Bana bir kahve ısmarla!", "buy-me-a-coffee_title": "Bana bir kahve al!",
"github_title": "GitHub'da PairDrop", "github_title": "GitHub'da PairDrop",
"faq_title": "Sıkça sorulan sorular", "faq_title": "Sıkça sorulan sorular",
"custom_title": "Bizi takip edin", "custom_title": "Bizi takip edin",
"privacypolicy_title": "Gizlilik politikamızı aç", "privacypolicy_title": "Gizlilik politikamızıın",
"mastodon_title": "Mastodon'da PairDrop hakkında yaz", "mastodon_title": "Mastodon'da PairDrop hakkında yazın",
"bluesky_title": "BlueSky'da bizi takip edin" "bluesky_title": "Bizi BlueSky'da takip edin"
}, },
"document-titles": { "document-titles": {
"file-transfer-requested": "Dosya Transferi Talep Edildi", "file-transfer-requested": "Dosya Transferi Talep Edildi",
"image-transfer-requested": "Görüntü Transferi Talep Edildi", "image-transfer-requested": "Görüntü Aktarımı Talep Edildi",
"message-received-plural": "{{count}} Mesaj Alındı", "message-received-plural": "{{count}} Mesaj Alındı",
"message-received": "Mesaj Alındı", "message-received": "Mesaj Alındı",
"file-received": "Dosya Alındı", "file-received": "Dosya Alındı",
-1
View File
@@ -1 +0,0 @@
{}
-184
View File
@@ -1,184 +0,0 @@
{
"header": {
"language-selector_title": "設定語言",
"theme-auto_title": "自動使用系統主題",
"cancel-share-mode": "取消",
"edit-share-mode": "編輯",
"expand_title": "展開標題按鈕列",
"about_title": "關於 PairDrop",
"about_aria-label": "開啟關於 PairDrop",
"theme-dark_title": "總是使用深色主題",
"notification_title": "啟用通知",
"install_title": "安裝 PairDrop",
"pair-device_title": "永久配對你的裝置",
"edit-paired-devices_title": "編輯已配對的裝置",
"join-public-room_title": "暫時加入公共房間",
"theme-light_title": "總是使用淺色主題"
},
"instructions": {
"no-peers_data-drop-bg": "釋放以選擇接收者",
"no-peers-subtitle": "配對裝置或加入一個公共房間以便在其他網路上可見",
"x-instructions_mobile": "輕觸以發送檔案或輕觸並按住以發送一則訊息",
"x-instructions_data-drop-bg": "釋放以選擇接收者",
"x-instructions-share-mode_desktop": "點擊以發送 {{descriptor}}",
"activate-share-mode-and-other-file": "和另外 1 個檔案",
"activate-share-mode-and-other-files-plural": "和另外 {{count}} 個檔案",
"activate-share-mode-shared-text": "已分享的文字",
"activate-share-mode-shared-file": "已分享的檔案",
"webrtc-requirement": "要使用此 PairDrop 實例,必須啟用 WebRTC",
"no-peers-title": "在其他裝置上開啟 PairDrop 以傳送檔案",
"x-instructions_desktop": "點擊以發送檔案或右鍵點擊以發送一則訊息",
"x-instructions_data-drop-peer": "釋放以發送到此裝置",
"x-instructions-share-mode_mobile": "輕觸以發送 {{descriptor}}",
"activate-share-mode-base": "在其他裝置上開啟 PairDrop 以傳送",
"activate-share-mode-shared-files-plural": "{{count}} 個已分享的檔案"
},
"footer": {
"display-name_data-placeholder": "正在載入中…",
"known-as": "此裝置名稱為:",
"display-name_title": "編輯此裝置名稱並儲存",
"discovery": "可見於:",
"on-this-network": "在這個網路上",
"paired-devices_title": "無論在任何網路,已配對的裝置都是隨時可見。",
"public-room-devices": "在房間 {{roomId}} 中",
"traffic": "流量將會",
"on-this-network_title": "在這個網路上的每個人都可以見到你。",
"paired-devices": "已配對的裝置",
"public-room-devices_title": "無論在任何網路,此公共房間中的裝置都是隨時可見。",
"routed": "途經伺服器",
"webrtc": "如果無法使用 WebRTC。"
},
"dialogs": {
"pair-devices-title": "永久配對裝置",
"scan-qr-code": "或掃描 QR 圖碼。",
"input-key-on-this-device": "在另一台裝置上輸入此密鑰",
"temporary-public-room-title": "臨時公共房間",
"input-room-id-on-another-device": "在另一台裝置上輸入此房間編號",
"enter-room-id-from-another-device": "在此輸入房間編號以加入房間。",
"hr-or": "或者",
"pair": "配對",
"cancel": "取消",
"paired-devices-wrapper_data-empty": "沒有已配對的裝置。",
"auto-accept-instructions-1": "啟用",
"auto-accept": "自動接收",
"auto-accept-instructions-2": "以自動接受從該裝置發送的所有檔案。",
"close": "關閉",
"join": "加入",
"send": "發送",
"receive-text-title": "收到訊息",
"base64-tap-to-paste": "輕觸此處以分享 {{type}}",
"base64-text": "文字",
"base64-files": "檔案",
"file-other-description-file": "和另外 1 個檔案",
"file-other-description-image-plural": "和另外 {{count}} 張圖片",
"file-other-description-file-plural": "和另外 {{count}} 個檔案",
"title-image": "圖片",
"title-file": "檔案",
"title-image-plural": "圖片",
"title-file-plural": "檔案",
"receive-title": "收到 {{descriptor}}",
"download-again": "再次下載",
"language-selector-title": "設定語言",
"system-language": "使用系統語言",
"public-room-qr-code_title": "點擊以複製連結到公共房間",
"pair-devices-qr-code_title": "點擊以複製連結以配對此裝置",
"approve": "批准",
"share-text-title": "分享文字訊息",
"share-text-subtitle": "發送前編輯訊息:",
"share-text-checkbox": "分享文字時總是顯示此對話框",
"enter-key-from-another-device": "在此輸入另一台裝置的密鑰。",
"edit-paired-devices-title": "編輯已配對的裝置",
"unpair": "取消配對",
"paired-device-removed": "已刪除已配對的裝置。",
"leave": "離開",
"accept": "接受",
"would-like-to-share": "想要分享",
"decline": "拒絕",
"has-sent": "已發送:",
"share": "分享",
"message_title": "輸入要傳送的訊息",
"download": "下載",
"send-message-to": "發送至:",
"message_placeholder": "文字",
"copy": "複製",
"send-message-title": "發送訊息",
"base64-title-files": "分享檔案",
"base64-title-text": "分享文字",
"base64-processing": "正在處理中…",
"base64-paste-to-send": "將剪貼簿貼到此處以分享 {{type}}",
"file-other-description-image": "和另外 1 張圖片",
"close-toast_title": "關閉通知"
},
"about": {
"claim": "跨裝置傳輸檔案的最簡單方法",
"github_title": "GitHub 上的 PairDrop",
"tweet_title": "發佈關於 PairDrop 的推文",
"mastodon_title": "在 Mastodon 上撰寫有關 PairDrop 的文章",
"close-about_aria-label": "關閉關於 PairDrop",
"buy-me-a-coffee_title": "給我買一杯咖啡吧!",
"bluesky_title": "在 Bluesky 上關注我們",
"custom_title": "關注我們",
"privacypolicy_title": "開啟我們的隱私權政策",
"faq_title": "常見問題"
},
"notifications": {
"display-name-changed-permanently": "裝置名稱已更改並儲存",
"display-name-changed-temporarily": "裝置名稱已更改並只限於此會話",
"display-name-random-again": "裝置名稱已再次隨機生成",
"download-successful": "{{descriptor}} 已下載",
"pairing-success": "裝置已配對",
"pairing-not-persistent": "已配對的裝置不是永久的",
"pairing-key-invalid": "無效的密鑰",
"pairing-key-invalidated": "密鑰 {{key}} 已失效",
"pairing-cleared": "所有裝置均已解除配對",
"public-room-id-invalid": "無效的房間編號",
"public-room-left": "已離開公共房間 {{publicRoomId}}",
"copied-to-clipboard": "已複製到剪貼簿",
"pair-url-copied-to-clipboard": "已將與此裝置配對的連結複製到剪貼簿",
"room-url-copied-to-clipboard": "已將公共房間的連結複製到剪貼簿",
"copied-to-clipboard-error": "無法複製。請手動複製。",
"text-content-incorrect": "文字內容不正確",
"file-content-incorrect": "檔案內容不正確",
"notifications-enabled": "已啟用通知",
"notifications-permissions-error": "由於使用者多次取消權限請求提示,通知權限已被封鎖。這可以在頁面資訊中重置,點擊網址欄旁邊的鎖定圖示以存取頁面資訊。",
"link-received": "收到 {{name}} 的連結—點擊以開啟",
"message-received": "收到 {{name}} 的訊息—點擊以複製",
"click-to-show": "點擊以顯示",
"copied-text": "已將文字複製到剪貼簿",
"copied-text-error": "無法複製到剪貼簿。請手動複製!",
"offline": "已離線",
"online": "已重新在線",
"connected": "已連接",
"online-requirement-pairing": "你需要在線才能配對裝置",
"online-requirement-public-room": "你需要在線才能創建公共房間",
"connecting": "正在連接中…",
"files-incorrect": "檔案不正確",
"file-transfer-completed": "檔案傳輸已完成",
"ios-memory-limit": "向 iOS 發送檔案每次只能最大 200 MB",
"message-transfer-completed": "訊息傳輸已完成",
"unfinished-transfers-warning": "還有未完成的傳輸。你確定要關閉 PairDrop 嗎?",
"rate-limit-join-key": "已達到速率限制。請等待 10 秒鐘,然後再試一次。",
"selected-peer-left": "選定的對象已離開",
"pairing-tabs-error": "將兩個網頁瀏覽器分頁進行配對是不可能的",
"clipboard-content-incorrect": "剪貼簿內容不正確",
"click-to-download": "點擊以下載",
"request-title": "{{name}} 想要發送 {{count}} 個 {{descriptor}}"
},
"document-titles": {
"file-received": "檔案已收到",
"file-received-plural": "已收到 {{count}} 個檔案",
"file-transfer-requested": "已請求檔案傳輸",
"image-transfer-requested": "已請求圖片傳輸",
"message-received": "已收到訊息",
"message-received-plural": "已收到 {{count}} 條訊息"
},
"peer-ui": {
"click-to-send-share-mode": "點擊以發送 {{descriptor}}",
"click-to-send": "點擊以發送檔案或右鍵點擊以發送一則訊息",
"preparing": "正在準備中…",
"waiting": "正在等待中…",
"processing": "正在處理中…",
"transferring": "正在傳輸中…",
"connection-hash": "若要驗證端對端加密的安全性,請在兩台裝置上比較此安全編號"
}
}
+1 -1
View File
@@ -28,7 +28,7 @@
"background_color": "#efefef", "background_color": "#efefef",
"start_url": "/", "start_url": "/",
"scope": "/", "scope": "/",
"display": "standalone", "display": "minimal-ui",
"theme_color": "#3367d6", "theme_color": "#3367d6",
"screenshots" : [ "screenshots" : [
{ {
+6 -20
View File
@@ -2,27 +2,18 @@ class BrowserTabsConnector {
constructor() { constructor() {
this.bc = new BroadcastChannel('pairdrop'); this.bc = new BroadcastChannel('pairdrop');
this.bc.addEventListener('message', e => this._onMessage(e)); this.bc.addEventListener('message', e => this._onMessage(e));
Events.on('broadcast-send', e => this._broadcastSend(e.detail.type, e.detail.data)); Events.on('broadcast-send', e => this._broadcastSend(e.detail));
Events.on('broadcast-self-display-name-changed', e => this._onBroadcastSelfDisplayNameChanged(e.detail.displayName));
} }
_broadcastSend(type, data) { _broadcastSend(message) {
this.bc.postMessage({ type, data }); this.bc.postMessage(message);
}
_onBroadcastSelfDisplayNameChanged(displayName) {
this._broadcastSend('self-display-name-changed', { displayName: displayName });
} }
_onMessage(e) { _onMessage(e) {
const type = e.data.type; Logger.debug('Broadcast:', e.data)
const data = e.data.data; switch (e.data.type) {
Logger.debug('Broadcast:', type, data);
switch (type) {
case 'self-display-name-changed': case 'self-display-name-changed':
Events.fire('self-display-name-changed', data.displayName); Events.fire('self-display-name-changed', e.data.detail);
break; break;
} }
} }
@@ -34,11 +25,6 @@ class BrowserTabsConnector {
: false; : false;
} }
static isOnlyTab() {
let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
return peerIdsBrowser.length <= 1;
}
static async addPeerIdToLocalStorage() { static async addPeerIdToLocalStorage() {
const peerId = sessionStorage.getItem('peer_id'); const peerId = sessionStorage.getItem('peer_id');
if (!peerId) return false; if (!peerId) return false;
+16 -16
View File
@@ -2,28 +2,28 @@ class Localization {
constructor() { constructor() {
Localization.$htmlRoot = document.querySelector('html'); Localization.$htmlRoot = document.querySelector('html');
Localization.localeDefault = "en"; Localization.defaultLocale = "en";
Localization.localesSupported = ["ar", "be", "ca", "da", "de", "en", "es", "fr", "he", "hu", "id", "it", "ja", "kn", "nb", "nl", "pl", "pt-BR", "ro", "ru", "tr", "zh-CN", "zh-TW"]; Localization.supportedLocales = ["ar", "ca", "de", "en", "es", "fr", "id", "it", "ja", "kn", "nb", "nl", "pt-BR", "ro", "ru", "tr", "zh-CN"];
Localization.localesRtl = ["ar", "he"]; Localization.supportedLocalesRtl = ["ar"];
Localization.translations = {}; Localization.translations = {};
Localization.translationsDefaultLocale = {}; Localization.translationsDefaultLocale = {};
Localization.localeSystem = Localization.getSupportedOrDefaultLocales(navigator.languages); Localization.systemLocale = Localization.getSupportedOrDefaultLocales(navigator.languages);
let storedLanguageCode = localStorage.getItem('language_code'); let storedLanguageCode = localStorage.getItem('language_code');
Localization.localeInitial = storedLanguageCode && Localization.localeIsSupported(storedLanguageCode) Localization.initialLocale = storedLanguageCode && Localization.localeIsSupported(storedLanguageCode)
? storedLanguageCode ? storedLanguageCode
: Localization.localeSystem; : Localization.systemLocale;
} }
static localeIsSupported(locale) { static localeIsSupported(locale) {
return Localization.localesSupported.indexOf(locale) > -1; return Localization.supportedLocales.indexOf(locale) > -1;
} }
static localeIsRtl(locale) { static localeIsRtl(locale) {
return Localization.localesRtl.indexOf(locale) > -1; return Localization.supportedLocalesRtl.indexOf(locale) > -1;
} }
static currentLocaleIsRtl() { static currentLocaleIsRtl() {
@@ -31,7 +31,7 @@ class Localization {
} }
static currentLocaleIsDefault() { static currentLocaleIsDefault() {
return Localization.locale === Localization.localeDefault return Localization.locale === Localization.defaultLocale
} }
static getSupportedOrDefaultLocales(locales) { static getSupportedOrDefaultLocales(locales) {
@@ -44,15 +44,15 @@ class Localization {
// If there is no perfect match for browser locales, try generic locales first before resorting to the default locale // If there is no perfect match for browser locales, try generic locales first before resorting to the default locale
return locales.find(Localization.localeIsSupported) return locales.find(Localization.localeIsSupported)
|| localesGeneric.find(Localization.localeIsSupported) || localesGeneric.find(Localization.localeIsSupported)
|| Localization.localeDefault; || Localization.defaultLocale;
} }
async setInitialTranslation() { async setInitialTranslation() {
await Localization.setTranslation(Localization.localeInitial) await Localization.setTranslation(Localization.initialLocale)
} }
static async setTranslation(locale) { static async setTranslation(locale) {
if (!locale) locale = Localization.localeSystem; if (!locale) locale = Localization.systemLocale;
await Localization.setLocale(locale) await Localization.setLocale(locale)
await Localization.translatePage(); await Localization.translatePage();
@@ -68,7 +68,7 @@ class Localization {
Logger.debug("Page successfully translated", Logger.debug("Page successfully translated",
`System language: ${Localization.localeSystem}`, `System language: ${Localization.systemLocale}`,
`Selected language: ${locale}` `Selected language: ${locale}`
); );
@@ -78,7 +78,7 @@ class Localization {
static async setLocale(newLocale) { static async setLocale(newLocale) {
if (newLocale === Localization.locale) return false; if (newLocale === Localization.locale) return false;
Localization.translationsDefaultLocale = await Localization.fetchTranslationsFor(Localization.localeDefault); Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale);
const newTranslations = await Localization.fetchTranslationsFor(newLocale); const newTranslations = await Localization.fetchTranslationsFor(newLocale);
@@ -192,7 +192,7 @@ class Localization {
else { else {
// Is not default locale yet // Is not default locale yet
// Get translation for default language with same arguments // Get translation for default language with same arguments
Logger.debug(`Using default language ${Localization.localeDefault.toUpperCase()} instead.`); Logger.debug(`Using default language ${Localization.defaultLocale.toUpperCase()} instead.`);
translation = this.getTranslation(key, attr, data, true); translation = this.getTranslation(key, attr, data, true);
} }
} }
@@ -202,7 +202,7 @@ class Localization {
static logTranslationMissingOrBroken(key, attr, data, useDefault) { static logTranslationMissingOrBroken(key, attr, data, useDefault) {
let usedLocale = useDefault let usedLocale = useDefault
? Localization.localeDefault.toUpperCase() ? Localization.defaultLocale.toUpperCase()
: Localization.locale.toUpperCase(); : Localization.locale.toUpperCase();
Logger.warn(`Missing or broken translation for language ${usedLocale}.\n`, 'key:', key, 'attr:', attr, 'data:', data); Logger.warn(`Missing or broken translation for language ${usedLocale}.\n`, 'key:', key, 'attr:', attr, 'data:', data);
+1 -2
View File
@@ -101,7 +101,7 @@ class PairDrop {
} }
onPwaInstallable(e) { onPwaInstallable(e) {
if (!window.matchMedia('(display-mode: standalone)').matches) { if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
// only display install btn when not installed // only display install btn when not installed
this.$headerInstallBtn.removeAttribute('hidden'); this.$headerInstallBtn.removeAttribute('hidden');
this.$headerInstallBtn.addEventListener('click', () => { this.$headerInstallBtn.addEventListener('click', () => {
@@ -137,7 +137,6 @@ class PairDrop {
let stylesheet = document.createElement('link'); let stylesheet = document.createElement('link');
stylesheet.rel = 'preload'; stylesheet.rel = 'preload';
stylesheet.as = 'style'; stylesheet.as = 'style';
stylesheet.defer = true;
stylesheet.href = url; stylesheet.href = url;
stylesheet.onload = _ => { stylesheet.onload = _ => {
stylesheet.onload = null; stylesheet.onload = null;
+171 -408
View File
@@ -69,7 +69,7 @@ class ServerConnection {
_connect() { _connect() {
clearTimeout(this._reconnectTimer); clearTimeout(this._reconnectTimer);
if (this._isConnected() || this._isConnecting()) return; if (this._isConnected() || this._isConnecting() || this._isOffline()) return;
if (this._isReconnect) { if (this._isReconnect) {
Events.fire('notify-user', { Events.fire('notify-user', {
message: Localization.getTranslation("notifications.connecting"), message: Localization.getTranslation("notifications.connecting"),
@@ -350,15 +350,6 @@ class Peer {
this._state = Peer.STATE_IDLE; this._state = Peer.STATE_IDLE;
this._busy = false; this._busy = false;
clearInterval(this._updateStatusTextInterval);
this._updateStatusTextInterval = null;
this._bytesTotal = 0;
this._bytesReceivedFiles = 0;
this._timeStartTransferComplete = null;
this._timeStartTransferFile = null;
this._byteLogs = [];
// tidy up sender // tidy up sender
this._filesRequested = null; this._filesRequested = null;
this._chunker = null; this._chunker = null;
@@ -366,12 +357,9 @@ class Peer {
// tidy up receiver // tidy up receiver
this._pendingRequest = null; this._pendingRequest = null;
this._acceptedRequest = null; this._acceptedRequest = null;
this._filesReceived = []; this._totalBytesReceived = 0;
if (this._digester) {
this._digester.cleanUp();
this._digester = null; this._digester = null;
} this._filesReceived = [];
// disable NoSleep if idle // disable NoSleep if idle
Events.fire('evaluate-no-sleep'); Events.fire('evaluate-no-sleep');
@@ -517,6 +505,9 @@ class Peer {
case 'transfer-header': case 'transfer-header':
this._onTransferHeader(message); this._onTransferHeader(message);
break; break;
case 'receive-progress':
this._onReceiveProgress(message.progress);
break;
case 'receive-confirmation': case 'receive-confirmation':
this._onReceiveConfirmation(message.bytesReceived); this._onReceiveConfirmation(message.bytesReceived);
break; break;
@@ -559,7 +550,7 @@ class Peer {
} }
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName}); Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
Events.fire('notify-display-name-changed', { recipient: this._peerId }); Events.fire('notify-peer-display-name-changed', this._peerId);
} }
_sendState() { _sendState() {
@@ -629,89 +620,9 @@ class Peer {
_abortTransfer() { _abortTransfer() {
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'error'}); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'error'});
if (this._digester) {
this._digester.abort();
}
this._reset(); this._reset();
} }
_addLog(bytesReceivedTotal) {
const now = Date.now();
// Add log
this._byteLogs.push({
time: now,
bytesReceived: bytesReceivedTotal
});
// Always include at least 5 entries (2.5 MB) to increase precision
if (this._byteLogs.length < 5) return;
// Move running average to calculate with a window of 20s
while (now - this._byteLogs[0].time > 20000) {
this._byteLogs.shift();
}
}
_updateStatusText() {
const secondsSinceStart = Math.round((Date.now() - this._timeStartTransferComplete) / 1000);
// Wait for 10s to only show info on longer transfers and to increase precision
if (secondsSinceStart < 10) return;
// mode: 0 -> speed, 1 -> time left, 2 -> receive/transfer (statusText = null)
const mode = Math.round((secondsSinceStart - 10) / 5) % 3;
let statusText = null;
if (mode === 0) {
statusText = this._getSpeedString();
}
else if (mode === 1) {
statusText = this._getTimeString();
}
this._statusText = statusText;
}
_getSpeedKbPerSecond() {
const timeDifferenceSeconds = (this._byteLogs[this._byteLogs.length - 1].time - this._byteLogs[0].time) / 1000;
const bytesDifferenceKB = (this._byteLogs[this._byteLogs.length - 1].bytesReceived - this._byteLogs[0].bytesReceived) / 1000;
return Math.round(bytesDifferenceKB / timeDifferenceSeconds);
}
_getBytesLeft() {
return this._bytesTotal - this._byteLogs[this._byteLogs.length - 1].bytesReceived;
}
_getSecondsLeft() {
return Math.round(this._getBytesLeft() / this._getSpeedKbPerSecond() / 1000);
}
_getSpeedString() {
const speedKBs = this._getSpeedKbPerSecond();
if (speedKBs >= 1000) {
let speedMBs = Math.round(speedKBs / 100) / 10;
return `${speedMBs} MB/s`; // e.g. "2.2 MB/s"
}
return `${speedKBs} kB/s`; // e.g. "522 kB/s"
}
_getTimeString() {
const seconds = this._getSecondsLeft();
if (seconds > 60) {
let minutes = Math.floor(seconds / 60);
let secondsLeft = Math.floor(seconds % 60);
return `${minutes} min ${secondsLeft}s`; // e.g. // "1min 20s"
}
else {
return `${seconds}s`; // e.g. "35s"
}
}
// File Sender Only // File Sender Only
async _sendFileTransferRequest(files) { async _sendFileTransferRequest(files) {
this._state = Peer.STATE_PREPARE; this._state = Peer.STATE_PREPARE;
@@ -744,7 +655,6 @@ class Peer {
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'}); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'});
this._filesRequested = files; this._filesRequested = files;
this._bytesTotal = totalSize;
this._sendMessage({type: 'transfer-request', this._sendMessage({type: 'transfer-request',
header: header, header: header,
@@ -765,7 +675,7 @@ class Peer {
if (message.reason === 'ram-exceed-ios') { if (message.reason === 'ram-exceed-ios') {
Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios')); Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios'));
} }
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'idle'}); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._reset(); this._reset();
return; return;
} }
@@ -781,18 +691,7 @@ class Peer {
this._filesQueue.push(this._filesRequested[i]); this._filesQueue.push(this._filesRequested[i]);
} }
this._filesRequested = null this._filesRequested = null
if (this._busy) return; if (this._busy) return;
this._byteLogs = [];
this._bytesReceivedFiles = 0;
this._timeStartTransferComplete = Date.now();
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
this._statusText = null;
this._updateStatusTextInterval = setInterval(() => this._updateStatusText(), 1000);
this._dequeueFile(); this._dequeueFile();
} }
@@ -822,19 +721,21 @@ class Peer {
this._chunker._resendFromOffset(offset); this._chunker._resendFromOffset(offset);
} }
_onReceiveProgress(progress) {
if (this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
this._sendState();
return;
}
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
}
_onReceiveConfirmation(bytesReceived) { _onReceiveConfirmation(bytesReceived) {
if (!this._chunker || this._state !== Peer.STATE_TRANSFER_PROCEEDING) { if (!this._chunker || this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
this._sendState(); this._sendState();
return; return;
} }
this._chunker._onReceiveConfirmation(bytesReceived); this._chunker._onReceiveConfirmation(bytesReceived);
const bytesReceivedTotal = this._bytesReceivedFiles + bytesReceived;
const progress = Math.round(1e4 * bytesReceivedTotal / this._bytesTotal) / 1e4;
this._addLog(bytesReceivedTotal);
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer', statusText: this._statusText});
} }
_onFileReceiveComplete(message) { _onFileReceiveComplete(message) {
@@ -843,13 +744,11 @@ class Peer {
return; return;
} }
this._bytesReceivedFiles += this._chunker._file.size;
this._chunker = null; this._chunker = null;
if (!message.success) { if (!message.success) {
Logger.warn('File could not be sent'); Logger.warn('File could not be sent');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'idle'}); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._reset(); this._reset();
return; return;
} }
@@ -863,7 +762,7 @@ class Peer {
// No more files in queue. Transfer is complete // No more files in queue. Transfer is complete
this._reset(); this._reset();
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'transfer-complete'}); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer-complete'});
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed")); Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
} }
@@ -876,12 +775,8 @@ class Peer {
return; return;
} }
this.fileDigesterWorkerSupported = await SWFileDigester.isSupported();
Logger.debug('Digesting files via service workers is', this.fileDigesterWorkerSupported ? 'supported' : 'NOT supported');
// Check if each file must be loaded into RAM completely. This might lead to a page crash (Memory limit iOS Safari: ~380 MB) // Check if each file must be loaded into RAM completely. This might lead to a page crash (Memory limit iOS Safari: ~380 MB)
if (!this.fileDigesterWorkerSupported) { if (!(await FileDigesterWorker.isSupported())) {
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) and do not use private tabs to prevent this.'); Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) and do not use private tabs to prevent this.');
// Check if page will crash on iOS // Check if page will crash on iOS
@@ -927,24 +822,16 @@ class Peer {
message.reason = reason; message.reason = reason;
} }
this._sendMessage(message);
if (accepted) { if (accepted) {
this._state = Peer.STATE_RECEIVE_PROCEEDING; this._state = Peer.STATE_RECEIVE_PROCEEDING;
this._busy = true; this._busy = true;
this._byteLogs = [];
this._filesReceived = [];
this._acceptedRequest = this._pendingRequest; this._acceptedRequest = this._pendingRequest;
this._lastProgress = 0;
this._bytesTotal = this._acceptedRequest.totalSize; this._totalBytesReceived = 0;
this._bytesReceivedFiles = 0; this._filesReceived = [];
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'receive'});
this._timeStartTransferComplete = Date.now();
this._statusText = null;
this._updateStatusTextInterval = setInterval(() => this._updateStatusText(), 1000);
} }
this._sendMessage(message);
} }
_onTransferHeader(header) { _onTransferHeader(header) {
@@ -960,42 +847,20 @@ class Peer {
return; return;
} }
this._timeStartTransferFile = Date.now(); this._timeStart = Date.now();
this._addFileDigester(header); this._addFileDigester(header);
} }
_addFileDigester(header) { _addFileDigester(header) {
this._digester = this.fileDigesterWorkerSupported this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime},
? new FileDigesterViaWorker( fileBlob => this._fileReceived(fileBlob),
{
size: header.size,
name: header.name,
mime: header.mime
},
file => this._fileReceived(file),
bytesReceived => this._sendReceiveConfirmation(bytesReceived)
)
: new FileDigesterViaBuffer(
{
size: header.size,
name: header.name,
mime: header.mime
},
file => this._fileReceived(file),
bytesReceived => this._sendReceiveConfirmation(bytesReceived) bytesReceived => this._sendReceiveConfirmation(bytesReceived)
); );
} }
_sendReceiveConfirmation(bytesReceived) { _sendReceiveConfirmation(bytesReceived) {
this._sendMessage({type: 'receive-confirmation', bytesReceived: bytesReceived}); this._sendMessage({type: 'receive-confirmation', bytesReceived: bytesReceived});
const bytesReceivedTotal = this._bytesReceivedFiles + bytesReceived;
const progress = Math.round(1e4 * bytesReceivedTotal / this._bytesTotal) / 1e4;
this._addLog(bytesReceivedTotal);
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'receive', statusText: this._statusText});
} }
_sendResendRequest(offset) { _sendResendRequest(offset) {
@@ -1022,7 +887,25 @@ class Peer {
catch (e) { catch (e) {
this._abortTransfer(); this._abortTransfer();
Logger.error(e); Logger.error(e);
return;
} }
// While transferring -> round progress to 4th digit. After transferring, set it to 1.
let progress = this._digester
? Math.floor(1e4 * (this._totalBytesReceived + this._digester._bytesReceived) / this._acceptedRequest.totalSize) / 1e4
: 1;
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'receive'});
// occasionally notify sender about our progress
if (progress - this._lastProgress >= 0.005 || progress === 1) {
this._lastProgress = progress;
this._sendProgress(progress);
}
}
_sendProgress(progress) {
this._sendMessage({ type: 'receive-progress', progress: progress });
} }
_fileReceived(file) { _fileReceived(file) {
@@ -1034,7 +917,6 @@ class Peer {
// We are done receiving // We are done receiving
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'receive'}); Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'receive'});
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
this._allFilesReceiveComplete(); this._allFilesReceiveComplete();
} }
@@ -1064,9 +946,9 @@ class Peer {
this._digester._sendReceiveConfimationCallback = null; this._digester._sendReceiveConfimationCallback = null;
this._digester = null; this._digester = null;
this._bytesReceivedFiles += file.size; this._totalBytesReceived += file.size;
const duration = (Date.now() - this._timeStartTransferFile) / 1000; // s const duration = (Date.now() - this._timeStart) / 1000; // s
const size = Math.round(10 * file.size / 1e6) / 10; // MB const size = Math.round(10 * file.size / 1e6) / 10; // MB
const speed = Math.round(100 * size / duration) / 100; // MB/s const speed = Math.round(100 * size / duration) / 100; // MB/s
@@ -1074,7 +956,7 @@ class Peer {
Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`); Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`);
// include for compatibility with 'Snapdrop & PairDrop for Android' app // include for compatibility with 'Snapdrop & PairDrop for Android' app
Events.fire('file-received', {name: file.originalName, size: file.size}); Events.fire('file-received', file);
this._filesReceived.push(file); this._filesReceived.push(file);
@@ -1147,6 +1029,33 @@ class RTCPeer extends Peer {
); );
} }
async _connectionType() {
if (!this._conn) return "";
const stats = await this._conn.getStats(null);
let id;
stats.forEach((report) => {
if (report.type === "candidate-pair" && report.state === "succeeded") {
id = report.localCandidateId;
}
});
if (!id) return "";
let connectionType;
stats.forEach((report) => {
if (report.id === id) {
connectionType = report.candidateType;
}
});
return connectionType;
}
async _isTurn() {
return await this._connectionType() === "relay";
}
_messageChannelOpen() { _messageChannelOpen() {
return this._messageChannel && this._messageChannel.readyState === 'open'; return this._messageChannel && this._messageChannel.readyState === 'open';
} }
@@ -1276,13 +1185,13 @@ class RTCPeer extends Peer {
return channel; return channel;
} }
_onChannelOpened(e) { async _onChannelOpened(e) {
Logger.debug(`RTC: Channel ${e.target.label} opened with`, this._peerId); Logger.debug(`RTC: Channel ${e.target.label} opened with`, this._peerId);
// wait until all channels are open // wait until all channels are open
if (!this._stable()) return; if (!this._stable()) return;
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()}); Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash(), connectionType: await this._connectionType()});
super._onPeerConnected(); super._onPeerConnected();
this._sendPendingOutboundMessaged(); this._sendPendingOutboundMessaged();
@@ -1605,53 +1514,35 @@ class WSPeer extends Peer {
class PeersManager { class PeersManager {
constructor(serverConnection) { constructor(serverConnection) {
this._server = serverConnection;
this.peers = {}; this.peers = {};
this._device = { this._server = serverConnection;
originalDisplayName: '',
displayName: '',
publicRoomId: null
};
Events.on('signal', e => this._onSignal(e.detail)); Events.on('signal', e => this._onSignal(e.detail));
Events.on('peers', e => this._onPeers(e.detail)); Events.on('peers', e => this._onPeers(e.detail));
Events.on('files-selected', e => this._onFilesSelected(e.detail));
Events.on('respond-to-files-transfer-request', e => this._onRespondToFileTransferRequest(e.detail))
Events.on('send-text', e => this._onSendText(e.detail));
Events.on('peer-left', e => this._onPeerLeft(e.detail)); Events.on('peer-left', e => this._onPeerLeft(e.detail));
Events.on('peer-joined', e => this._onPeerJoined(e.detail)); Events.on('peer-joined', e => this._onPeerJoined(e.detail));
Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId)); Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId));
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
// ROOMS
Events.on('join-public-room', e => this._onJoinPublicRoom(e.detail.roomId));
// this device closes connection // this device closes connection
Events.on('room-secrets-deleted', e => this._onRoomSecretsDeleted(e.detail)); Events.on('room-secrets-deleted', e => this._onRoomSecretsDeleted(e.detail));
Events.on('leave-public-room', _ => this._onLeavePublicRoom()); Events.on('leave-public-room', e => this._onLeavePublicRoom(e.detail));
// peer closes connection // peer closes connection
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail)); Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail)); Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail));
// peer
Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('display-name', e => this._onDisplayName(e.detail.displayName));
Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail.displayName)); Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail));
Events.on('notify-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail.recipient)); Events.on('notify-peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail));
Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept)); Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept));
// transfer
Events.on('send-text', e => this._onSendText(e.detail));
Events.on('files-selected', e => this._onFilesSelected(e.detail));
Events.on('respond-to-files-transfer-request', e => this._onRespondToFileTransferRequest(e.detail))
// websocket connection
Events.on('ws-disconnected', _ => this._onWsDisconnected()); Events.on('ws-disconnected', _ => this._onWsDisconnected());
Events.on('ws-relay', e => this._onWsRelay(e.detail.peerId, e.detail.message)); Events.on('ws-relay', e => this._onWsRelay(e.detail.peerId, e.detail.message));
Events.on('ws-config', e => this._onWsConfig(e.detail)); Events.on('ws-config', e => this._onWsConfig(e.detail));
// no-sleep
Events.on('evaluate-no-sleep', _ => this._onEvaluateNoSleep()); Events.on('evaluate-no-sleep', _ => this._onEvaluateNoSleep());
// clean up on page hide
Events.on('pagehide', _ => this._onPageHide());
} }
_onWsConfig(wsConfig) { _onWsConfig(wsConfig) {
@@ -1672,13 +1563,6 @@ class PeersManager {
NoSleepUI.disable(); NoSleepUI.disable();
} }
_onPageHide() {
// Clear OPFS directory ONLY if this is the last PairDrop Browser tab
if (!BrowserTabsConnector.isOnlyTab()) return;
SWFileDigester.clearDirectory();
}
_refreshPeer(isCaller, peerId, roomType, roomId) { _refreshPeer(isCaller, peerId, roomType, roomId) {
const peer = this.peers[peerId]; const peer = this.peers[peerId];
const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType; const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType;
@@ -1812,26 +1696,14 @@ class PeersManager {
} }
} }
_onJoinPublicRoom(roomId) { _onLeavePublicRoom(publicRoomId) {
if (roomId !== this._device.publicRoomId) { this._disconnectOrRemoveRoomTypeByRoomId('public-id', publicRoomId);
this._disconnectFromPublicRoom();
}
this._device.publicRoomId = roomId;
}
_onLeavePublicRoom() {
this._disconnectFromPublicRoom();
} }
_onSecretRoomDeleted(roomSecret) { _onSecretRoomDeleted(roomSecret) {
this._disconnectOrRemoveRoomTypeByRoomId('secret', roomSecret); this._disconnectOrRemoveRoomTypeByRoomId('secret', roomSecret);
} }
_disconnectFromPublicRoom() {
this._disconnectOrRemoveRoomTypeByRoomId('public-id', this._device.publicRoomId);
this._device.publicRoomId = null;
}
_disconnectOrRemoveRoomTypeByRoomId(roomType, roomId) { _disconnectOrRemoveRoomTypeByRoomId(roomType, roomId) {
const peerIds = this._getPeerIdsFromRoomId(roomId); const peerIds = this._getPeerIdsFromRoomId(roomId);
@@ -1845,7 +1717,7 @@ class PeersManager {
_disconnectOrRemoveRoomTypeByPeerId(peerId, roomType) { _disconnectOrRemoveRoomTypeByPeerId(peerId, roomType) {
const peer = this.peers[peerId]; const peer = this.peers[peerId];
if (!peer || !peer._getRoomTypes().includes(roomType)) return; if (!peer) return;
if (peer._getRoomTypes().length > 1) { if (peer._getRoomTypes().length > 1) {
peer._removeRoomType(roomType); peer._removeRoomType(roomType);
@@ -1865,10 +1737,7 @@ class PeersManager {
} }
_notifyPeersDisplayNameChanged(newDisplayName) { _notifyPeersDisplayNameChanged(newDisplayName) {
this._device.displayName = newDisplayName this._displayName = newDisplayName ? newDisplayName : this._originalDisplayName;
? newDisplayName
: this._device.originalDisplayName;
for (const peerId in this.peers) { for (const peerId in this.peers) {
this._notifyPeerDisplayNameChanged(peerId); this._notifyPeerDisplayNameChanged(peerId);
} }
@@ -1877,35 +1746,23 @@ class PeersManager {
_notifyPeerDisplayNameChanged(peerId) { _notifyPeerDisplayNameChanged(peerId) {
const peer = this.peers[peerId]; const peer = this.peers[peerId];
if (!peer) return; if (!peer) return;
this.peers[peerId]._sendDisplayName(this._device.displayName); this.peers[peerId]._sendDisplayName(this._displayName);
} }
_onDisplayName(displayName) { _onDisplayName(displayName) {
this._device.originalDisplayName = displayName; this._originalDisplayName = displayName;
// if the displayName has not been changed (yet) set the displayName to the original displayName // if the displayName has not been changed (yet) set the displayName to the original displayName
if (!this._device.displayName) this._device.displayName = displayName; if (!this._displayName) this._displayName = displayName;
} }
_onAutoAcceptUpdated(roomSecret, autoAccept) { _onAutoAcceptUpdated(roomSecret, autoAccept) {
let peerIds = this._getPeerIdsFromRoomId(roomSecret); const peerId = this._getPeerIdsFromRoomId(roomSecret)[0];
const peerId = this._removePeerIdsSameBrowser(peerIds)[0];
if (!peerId) return; if (!peerId) return;
this.peers[peerId]._setAutoAccept(autoAccept); this.peers[peerId]._setAutoAccept(autoAccept);
} }
_removePeerIdsSameBrowser(peerIds) {
let peerIdsNotSameBrowser = [];
for (let i = 0; i < peerIds.length; i++) {
const peer = this.peers[peerIds[i]];
if (!peer._isSameBrowser()) {
peerIdsNotSameBrowser.push(peerIds[i]);
}
}
return peerIdsNotSameBrowser;
}
_getPeerIdsFromRoomId(roomId) { _getPeerIdsFromRoomId(roomId) {
if (!roomId) return []; if (!roomId) return [];
@@ -2028,6 +1885,7 @@ class FileChunkerWS extends FileChunker {
class FileDigester { class FileDigester {
constructor(meta, fileCompleteCallback, sendReceiveConfirmationCallback) { constructor(meta, fileCompleteCallback, sendReceiveConfirmationCallback) {
this._buffer = [];
this._bytesReceived = 0; this._bytesReceived = 0;
this._bytesReceivedSinceLastTime = 0; this._bytesReceivedSinceLastTime = 0;
this._maxBytesWithoutConfirmation = 1048576; // 1 MB this._maxBytesWithoutConfirmation = 1048576; // 1 MB
@@ -2038,9 +1896,8 @@ class FileDigester {
this._sendReceiveConfimationCallback = sendReceiveConfirmationCallback; this._sendReceiveConfimationCallback = sendReceiveConfirmationCallback;
} }
unchunk(chunk) {} unchunk(chunk) {
this._buffer.push(chunk);
evaluateChunkSize(chunk) {
this._bytesReceived += chunk.byteLength; this._bytesReceived += chunk.byteLength;
this._bytesReceivedSinceLastTime += chunk.byteLength; this._bytesReceivedSinceLastTime += chunk.byteLength;
@@ -2053,151 +1910,70 @@ class FileDigester {
this._sendReceiveConfimationCallback(this._bytesReceived); this._sendReceiveConfimationCallback(this._bytesReceived);
this._bytesReceivedSinceLastTime = 0; this._bytesReceivedSinceLastTime = 0;
} }
}
isFileReceivedCompletely() { // File not completely received -> Wait for next chunk.
return this._bytesReceived >= this._size; if (this._bytesReceived < this._size) return;
}
cleanUp() {}
abort() {}
}
class FileDigesterViaBuffer extends FileDigester {
constructor(meta, fileCompleteCallback, sendReceiveConfirmationCallback) {
super(meta, fileCompleteCallback, sendReceiveConfirmationCallback);
this._buffer = [];
}
unchunk(chunk) {
this._buffer.push(chunk);
this.evaluateChunkSize(chunk);
// If file is not completely received -> Wait for next chunk.
if (!this.isFileReceivedCompletely()) return;
// We are done receiving. Preferably use a file worker to process the file to prevent exceeding of available RAM
FileDigesterWorker.isSupported()
.then(supported => {
if (!supported) {
this.processFileViaMemory(); this.processFileViaMemory();
return;
} }
processFileViaMemory() {
// Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
const file = new File(
this._buffer,
this._name,
{
type: this._mime,
lastModified: new Date().getTime()
}
);
this._fileCompleteCallback(file);
}
cleanUp() {
this._buffer = [];
}
abort() {
this.cleanUp();
}
}
class FileDigesterViaWorker extends FileDigester {
constructor(meta, fileCompleteCallback, sendReceiveConfirmationCallback) {
super(meta, fileCompleteCallback, sendReceiveConfirmationCallback);
this._fileDigesterWorker = new SWFileDigester();
}
unchunk(chunk) {
this._fileDigesterWorker
.nextChunk(chunk, this._bytesReceived)
.then(_ => {
this.evaluateChunkSize(chunk);
// If file is not completely received -> Wait for next chunk.
if (!this.isFileReceivedCompletely()) return;
this.processFileViaWorker(); this.processFileViaWorker();
}); });
} }
processFileViaWorker() { processFileViaMemory() {
this._fileDigesterWorker // Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
.getFile() const file = new File(this._buffer, this._name, {
.then(file => { type: this._mime,
// Save id and displayName to file to be able to truncate file later lastModified: new Date().getTime()
file.id = file.name; })
file.originalName = this._name; this._fileCompleteCallback(file);
}
processFileViaWorker() {
const fileDigesterWorker = new FileDigesterWorker();
fileDigesterWorker.digestFileBuffer(this._buffer, this._name)
.then(file => {
this._fileCompleteCallback(file); this._fileCompleteCallback(file);
}) })
.catch(e => { .catch(reason => {
Logger.error("Error in SWFileDigester:", e); Logger.warn(reason);
this.cleanUp(); this.processFileViaWorker();
}); })
}
cleanUp() {
this._fileDigesterWorker.cleanUp();
}
abort() {
// delete and clean up (included in deletion)
this._fileDigesterWorker.deleteFile().then((id) => {
Logger.debug("File deleted after abort:", id);
});
} }
} }
class FileDigesterWorker {
class SWFileDigester { constructor() {
static fileWorkers = [];
constructor(id = null) {
// Use service worker to prevent loading the complete file into RAM // Use service worker to prevent loading the complete file into RAM
// Uses origin private file system (OPFS) as storage endpoint this.fileWorker = new Worker("scripts/sw-file-digester.js");
if (!id) {
// Generate random uuid to save file on disk
// Create only one service worker per file to prevent problems with accessHandles
id = generateUUID();
SWFileDigester.fileWorkers[id] = new Worker("scripts/sw-file-digester.js");
}
this.id = id;
this.fileWorker = SWFileDigester.fileWorkers[id];
this.fileWorker.onmessage = (e) => { this.fileWorker.onmessage = (e) => {
switch (e.data.type) { switch (e.data.type) {
case "support": case "support":
this.onSupport(e.data.supported); this.onSupport(e.data.supported);
break; break;
case "chunk-written": case "part":
this.onChunkWritten(e.data.offset); this.onPart(e.data.part);
break; break;
case "file": case "file":
this.onFile(e.data.file); this.onFile(e.data.file);
break; break;
case "file-deleted": case "file-deleted":
this.onFileDeleted(e.data.id); this.onFileDeleted();
break; break;
case "error": case "error":
this.onError(e.data.error); this.onError(e.data.error);
break; break;
case "directory-cleared":
this.onDirectoryCleared();
break;
} }
} }
} }
onError(error) {
// an error occurred.
Logger.error(error);
}
static isSupported() { static isSupported() {
// Check if web worker is supported and supports specific functions // Check if web worker is supported and supports specific functions
return new Promise(async resolve => { return new Promise(async resolve => {
@@ -2206,7 +1982,7 @@ class SWFileDigester {
return; return;
} }
const fileDigesterWorker = new SWFileDigester(); const fileDigesterWorker = new FileDigesterWorker();
resolve(await fileDigesterWorker.checkSupport()); resolve(await fileDigesterWorker.checkSupport());
@@ -2230,88 +2006,75 @@ class SWFileDigester {
this.resolveSupport = null; this.resolveSupport = null;
} }
nextChunk(chunk, offset) { digestFileBuffer(buffer, fileName) {
return new Promise(resolve => { return new Promise((resolve, reject) => {
this.digestChunk(chunk, offset); this.resolveFile = resolve;
resolve(); this.rejectFile = reject;
});
this.i = 0;
this.offset = 0;
this.buffer = buffer;
this.fileName = fileName;
this.sendPart(this.buffer[0], 0);
})
} }
digestChunk(chunk, offset) {
sendPart(buffer, offset) {
this.fileWorker.postMessage({ this.fileWorker.postMessage({
type: "chunk", type: "part",
id: this.id, name: this.fileName,
chunk: chunk, buffer: buffer,
offset: offset offset: offset
}); });
} }
onChunkWritten(chunkOffset) {
Logger.debug("Chunk written at offset", chunkOffset);
}
getFile() { getFile() {
return new Promise(resolve => {
this.resolveFile = resolve;
this.fileWorker.postMessage({ this.fileWorker.postMessage({
type: "get-file", type: "get-file",
id: this.id, name: this.fileName,
}); });
})
}
async getFileById(id) {
const swFileDigester = new SWFileDigester(id);
return await swFileDigester.getFile();
}
onFile(file) {
this.resolveFile(file);
} }
deleteFile() { deleteFile() {
return new Promise(resolve => {
this.resolveDeletion = resolve;
this.fileWorker.postMessage({ this.fileWorker.postMessage({
type: "delete-file", type: "delete-file",
id: this.id name: this.fileName
}); })
});
} }
static async deleteFileById(id) { onPart(part) {
const swFileDigester = new SWFileDigester(id); if (this.i < this.buffer.length - 1) {
return await swFileDigester.deleteFile(); // process next chunk
this.offset += part.byteLength;
this.i++;
this.sendPart(this.buffer[this.i], this.offset);
return;
} }
cleanUp() { // File processing complete -> retrieve completed file
// terminate service worker this.getFile();
this.fileWorker.terminate();
delete SWFileDigester.fileWorkers[this.id];
} }
onFileDeleted(id) { onFile(file) {
this.buffer = [];
this.resolveFile(file);
this.deleteFile();
}
onFileDeleted() {
// File Digestion complete -> Tidy up // File Digestion complete -> Tidy up
Logger.debug("File deleted:", id); this.fileWorker.terminate();
this.resolveDeletion(id);
this.cleanUp();
} }
static clearDirectory() { onError(error) {
for (let i = 0; i < SWFileDigester.fileWorkers.length; i++) { // an error occurred.
SWFileDigester.fileWorkers[i].terminate(); Logger.error(error);
}
SWFileDigester.fileWorkers = [];
const swFileDigester = new SWFileDigester(); // Use memory method instead and terminate service worker.
swFileDigester.fileWorker.postMessage({ this.fileWorker.terminate();
type: "clear-directory", this.rejectFile("Failed to process file via service-worker. Do not use Firefox private mode to prevent this.");
});
}
onDirectoryCleared() {
Logger.debug("All files on OPFS truncated.");
this.cleanUp();
} }
} }
+26 -78
View File
@@ -1,104 +1,70 @@
self.accessHandle = undefined;
self.messageQueue = [];
self.busy = false;
self.addEventListener('message', async e => { self.addEventListener('message', async e => {
// Put message into queue if busy
if (self.busy) {
self.messageQueue.push(e.data);
return;
}
await digestMessage(e.data);
});
async function digestMessage(message) {
self.busy = true;
try { try {
switch (message.type) { switch (e.data.type) {
case "check-support": case "check-support":
await checkSupport(); await checkSupport();
break; break;
case "chunk": case "part":
await onChunk(message.id, message.chunk, message.offset); await onPart(e.data.name, e.data.buffer, e.data.offset);
break; break;
case "get-file": case "get-file":
await onGetFile(message.id); await onGetFile(e.data.name);
break; break;
case "delete-file": case "delete-file":
await onDeleteFile(message.id); await onDeleteFile(e.data.name);
break;
case "clear-directory":
await onClearDirectory();
break; break;
} }
} }
catch (e) { catch (e) {
self.postMessage({type: "error", error: e}); self.postMessage({type: "error", error: e});
} }
})
// message is digested. Digest next message.
await messageDigested();
}
async function messageDigested() {
if (!self.messageQueue.length) {
// no chunk in queue -> set flag to false and stop
this.busy = false;
return;
}
// Digest next message in queue
await this.digestMessage(self.messageQueue.pop());
}
async function checkSupport() { async function checkSupport() {
try { try {
const accessHandle = await getAccessHandle("test"); await getAccessHandle("test.txt");
self.postMessage({type: "support", supported: true}); self.postMessage({type: "support", supported: true});
accessHandle.close();
} }
catch (e) { catch (e) {
self.postMessage({type: "support", supported: false}); self.postMessage({type: "support", supported: false});
} }
} }
async function getFileHandle(id) { async function getFileHandle(fileName) {
const dirHandle = await navigator.storage.getDirectory(); const root = await navigator.storage.getDirectory();
return await dirHandle.getFileHandle(id, {create: true}); return await root.getFileHandle(fileName, {create: true});
} }
async function getAccessHandle(id) { async function getAccessHandle(fileName) {
const fileHandle = await getFileHandle(id); const fileHandle = await getFileHandle(fileName);
if (!self.accessHandle) {
// Create FileSystemSyncAccessHandle on the file. // Create FileSystemSyncAccessHandle on the file.
self.accessHandle = await fileHandle.createSyncAccessHandle(); return await fileHandle.createSyncAccessHandle();
} }
return self.accessHandle; async function onPart(fileName, buffer, offset) {
} const accessHandle = await getAccessHandle(fileName);
async function onChunk(id, chunk, offset) {
const accessHandle = await getAccessHandle(id);
// Write the message to the end of the file. // Write the message to the end of the file.
let encodedMessage = new DataView(chunk); let encodedMessage = new DataView(buffer);
accessHandle.write(encodedMessage, { at: offset }); accessHandle.write(encodedMessage, { at: offset });
self.postMessage({type: "chunk-written", offset: offset}); // Always close FileSystemSyncAccessHandle if done.
accessHandle.close(); accessHandle.close();
self.postMessage({type: "part", part: encodedMessage});
encodedMessage = null;
} }
async function onGetFile(id) { async function onGetFile(fileName) {
const fileHandle = await getFileHandle(id); const fileHandle = await getFileHandle(fileName);
let file = await fileHandle.getFile(); let file = await fileHandle.getFile();
self.postMessage({type: "file", file: file}); self.postMessage({type: "file", file: file});
} }
async function onDeleteFile(id) { async function onDeleteFile(fileName) {
const accessHandle = await getAccessHandle(id); const accessHandle = await getAccessHandle(fileName);
// Truncate the file to 0 bytes // Truncate the file to 0 bytes
accessHandle.truncate(0); accessHandle.truncate(0);
@@ -109,23 +75,5 @@ async function onDeleteFile(id) {
// Always close FileSystemSyncAccessHandle if done. // Always close FileSystemSyncAccessHandle if done.
accessHandle.close(); accessHandle.close();
self.postMessage({type: "file-deleted", id: id}); self.postMessage({type: "file-deleted"});
}
async function onClearDirectory() {
const dirHandle = await navigator.storage.getDirectory();
// Iterate through directory entries and truncate all entries to 0
for await (const [id, fileHandle] of dirHandle.entries()) {
const accessHandle = await fileHandle.createSyncAccessHandle();
// Truncate the file to 0 bytes
accessHandle.truncate(0);
// Persist changes to disk.
accessHandle.flush();
// Always close FileSystemSyncAccessHandle if done.
accessHandle.close();
}
} }
+6 -6
View File
@@ -205,7 +205,7 @@ class FooterUI {
this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('display-name', e => this._onDisplayName(e.detail.displayName));
Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail.displayName)); Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail));
// Load saved display name on page load // Load saved display name on page load
Events.on('ws-connected', _ => this._loadSavedDisplayName()); Events.on('ws-connected', _ => this._loadSavedDisplayName());
@@ -239,7 +239,7 @@ class FooterUI {
if (!displayName) return; if (!displayName) return;
Logger.debug("Retrieved edited display name:", displayName) Logger.debug("Retrieved edited display name:", displayName)
Events.fire('self-display-name-changed', { displayName: displayName }); Events.fire('self-display-name-changed', displayName);
} }
_onDisplayName(displayName){ _onDisplayName(displayName){
@@ -280,8 +280,8 @@ class FooterUI {
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
}) })
.finally(() => { .finally(() => {
Events.fire('self-display-name-changed', { displayName: newDisplayName }); Events.fire('self-display-name-changed', newDisplayName);
Events.fire('broadcast-self-display-name-changed', { displayName: newDisplayName }); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName});
}); });
} }
else { else {
@@ -292,8 +292,8 @@ class FooterUI {
}) })
.finally(() => { .finally(() => {
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
Events.fire('self-display-name-changed', { displayName: '' }); Events.fire('self-display-name-changed', '');
Events.fire('broadcast-self-display-name-changed', { displayName: '' }); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
}); });
} }
} }
+201 -285
View File
@@ -25,11 +25,11 @@ class PeersUI {
this.shareMode.text = ""; this.shareMode.text = "";
Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomType, e.detail.roomId)); Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomType, e.detail.roomId));
Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash)); Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash, e.detail.connectionType));
Events.on('peer-connecting', e => this._onPeerConnecting(e.detail)); Events.on('peer-connecting', e => this._onPeerConnecting(e.detail));
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
Events.on('peers', e => this._onPeers(e.detail)); Events.on('peers', e => this._onPeers(e.detail));
Events.on('set-progress', e => this._onSetProgress(e.detail.peerId, e.detail.progress, e.detail.status, e.detail.statusText)); Events.on('set-progress', e => this._onSetProgress(e.detail));
Events.on('drop', e => this._onDrop(e)); Events.on('drop', e => this._onDrop(e));
Events.on('keydown', e => this._onKeyDown(e)); Events.on('keydown', e => this._onKeyDown(e));
@@ -110,12 +110,12 @@ class PeersUI {
this.peerUIs[peer.id] = peerUI; this.peerUIs[peer.id] = peerUI;
} }
_onPeerConnected(peerId, connectionHash) { _onPeerConnected(peerId, connectionHash, connectionType) {
const peerUI = this.peerUIs[peerId]; const peerUI = this.peerUIs[peerId];
if (!peerUI) return; if (!peerUI) return;
peerUI._peerConnected(true, connectionHash); peerUI._peerConnected(true, connectionHash, connectionType);
this._addPeerUIIfMissing(peerUI); this._addPeerUIIfMissing(peerUI);
} }
@@ -185,43 +185,40 @@ class PeersUI {
}) })
} }
_onSetProgress(peerId, progress, status, statusText) { _onSetProgress(progress) {
const peerUI = this.peerUIs[peerId]; const peerUI = this.peerUIs[progress.peerId];
if (!peerUI) return; if (!peerUI) return;
peerUI.queueProgressStatus(progress, status, statusText); peerUI.setProgressOrQueue(progress.progress, progress.status);
} }
_onDrop(e) { _onDrop(e) {
if (this.shareMode.active || Dialog.anyDialogShown()) return;
e.preventDefault(); e.preventDefault();
if (this.shareMode.active || Dialog.anyDialogShown()) return;
if (!$$('x-peer') || !$$('x-peer').contains(e.target)) {
if (e.dataTransfer.files.length > 0) {
Events.fire('activate-share-mode', {files: e.dataTransfer.files});
} else {
for (let i=0; i<e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].type === "text/plain") {
e.dataTransfer.items[i].getAsString(text => {
Events.fire('activate-share-mode', {text: text});
});
}
}
}
}
this._onDragEnd(); this._onDragEnd();
if ($$('x-peer') || !$$('x-peer').contains(e.target)) return; // dropped on peer
const files = e.dataTransfer.files;
const text = e.dataTransfer.getData("text");
if (files.length > 0) {
Events.fire('activate-share-mode', {
files: files
});
}
else if(text.length > 0) {
Events.fire('activate-share-mode', {
text: text
});
}
} }
_onDragOver(e) { _onDragOver(e) {
if (this.shareMode.active || Dialog.anyDialogShown()) return;
e.preventDefault(); e.preventDefault();
if (this.shareMode.active || Dialog.anyDialogShown()) return;
this.$xInstructions.setAttribute('drop-bg', true); this.$xInstructions.setAttribute('drop-bg', true);
this.$xNoPeers.setAttribute('drop-bg', true); this.$xNoPeers.setAttribute('drop-bg', true);
} }
@@ -426,8 +423,8 @@ class PeerUI {
this._connected = false; this._connected = false;
this._currentProgress = 0; this._currentProgress = 0;
this._currentStatus = 'idle'; this._currentStatus = null
this._oldStatus = 'idle'; this._oldStatus = null;
this._progressQueue = []; this._progressQueue = [];
@@ -462,7 +459,11 @@ class PeerUI {
this.$displayName.textContent = this._displayName(); this.$displayName.textContent = this._displayName();
this.$deviceName.textContent = this._deviceName(); this.$deviceName.textContent = this._deviceName();
this.updateTypesClassList(); this._updateRoomTypeClasses();
this._updateSameBrowserClass();
this._updateWsPeerClass();
this.setStatus("connect");
this._evaluateShareMode(); this._evaluateShareMode();
this._bindListeners(); this._bindListeners();
@@ -473,14 +474,18 @@ class PeerUI {
} }
html() { html() {
let title= Localization.getTranslation("peer-ui.click-to-send"); const titleLabelTranslation= Localization.getTranslation("peer-ui.click-to-send");
const titleTurnTranslation= Localization.getTranslation("peer-ui.turn-indicator");
this.$el.innerHTML = ` this.$el.innerHTML = `
<label class="column center pointer" title="${title}"> <label class="column center pointer" title="${titleLabelTranslation}">
<input type="file" multiple/> <input type="file" multiple/>
<x-icon> <x-icon>
<div class="icon-wrapper" shadow="1"> <div class="icon-wrapper" shadow="1">
<svg class="icon"><use xlink:href="#"/></svg> <svg class="icon"><use xlink:href="#"/></svg>
<div class="turn-indicator-wrapper center" title="${titleTurnTranslation}">
<svg class="turn-indicator"><use xlink:href="#turn-indicator"/></svg>
</div>
</div> </div>
<div class="highlight-wrapper center"> <div class="highlight-wrapper center">
<div class="highlight highlight-room-ip" shadow="1"></div> <div class="highlight highlight-room-ip" shadow="1"></div>
@@ -500,30 +505,49 @@ class PeerUI {
</label>`; </label>`;
} }
updateTypesClassList() { _updateRoomTypeClasses() {
// Remove all classes // Remove all classes
this.$el.classList.remove('type-ip', 'type-secret', 'type-public-id', 'type-same-browser', 'ws-peer'); this.$el.classList.remove('type-ip', 'type-secret', 'type-public-id');
// Add classes accordingly // Add classes accordingly
Object.keys(this._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`)); Object.keys(this._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
if (BrowserTabsConnector.peerIsSameBrowser(this._peer.id)) {
this.$el.classList.add(`type-same-browser`);
} }
_updateSameBrowserClass() {
if (BrowserTabsConnector.peerIsSameBrowser(this._peer.id)) {
this.$el.classList.add('same-browser');
}
else {
this.$el.classList.remove('same-browser');
}
}
_updateWsPeerClass() {
if (!this._peer.rtcSupported || !window.isRtcSupported) { if (!this._peer.rtcSupported || !window.isRtcSupported) {
this.$el.classList.add('ws-peer'); this.$el.classList.add('ws-peer');
} }
else {
this.$el.classList.remove('ws-peer');
}
}
_updateTurnClass() {
if (this._connectionType === "relay") {
this.$el.classList.add('turn');
}
else {
this.$el.classList.remove('turn');
}
} }
_addRoomId(roomType, roomId) { _addRoomId(roomType, roomId) {
this._roomIds[roomType] = roomId; this._roomIds[roomType] = roomId;
this.updateTypesClassList(); this._updateRoomTypeClasses();
} }
_removeRoomId(roomType) { _removeRoomId(roomType) {
delete this._roomIds[roomType]; delete this._roomIds[roomType];
this.updateTypesClassList(); this._updateRoomTypeClasses();
} }
_onShareModeChanged(active = false, descriptor = "") { _onShareModeChanged(active = false, descriptor = "") {
@@ -599,26 +623,32 @@ class PeerUI {
}); });
} }
_peerConnected(connected = true, connectionHash = "") { _peerConnected(connected, connectionHash = "", connectionType = "") {
if (connected) { if (connected) {
this._connected = true; this._connected = true;
// on reconnect: reset status to saved status // on reconnect
this.queueProgressStatus(null, this._oldStatus); this.setStatus(this._oldStatus);
this._oldStatus = 'idle'; this._oldStatus = null;
this._connectionHash = connectionHash; this._connectionHash = connectionHash;
this._connectionType = connectionType;
this._updateSameBrowserClass();
this._updateWsPeerClass();
this._updateTurnClass();
} }
else { else {
this._connected = false; this._connected = false;
// when connecting: / connection is lost during transfer: save old status if (!this._oldStatus && this._currentStatus !== "connect") {
if (this._isTransferringStatus(this._currentStatus)) { // save old status when reconnecting
this._oldStatus = this._currentStatus; this._oldStatus = this._currentStatus;
} }
this.queueProgressStatus(null, "connect"); this.setStatus("connect");
this._connectionHash = ""; this._connectionHash = "";
this._connectionType = "";
} }
} }
@@ -691,125 +721,111 @@ class PeerUI {
$input.files = null; // reset input $input.files = null; // reset input
} }
queueProgressStatus(progress = null, status = null, statusText = null) { setProgressOrQueue(progress, status) {
clearTimeout(this._progressIdleTimeout); if (this._progressQueue.length > 0) {
// add to queue
this._progressQueue.push({progress: progress, status: status});
// if progress is higher than progress in queue -> overwrite in queue and cut queue at this position
for (let i = 0; i < this._progressQueue.length; i++) { for (let i = 0; i < this._progressQueue.length; i++) {
if (this._progressQueue[i].progress <= progress && this._progressQueue[i].status === status) { if (this._progressQueue[i].progress <= progress) {
this._progressQueue[i] = {progress, status, statusText}; // if progress is higher than progress in queue -> overwrite in queue and cut queue at this position
this._progressQueue.splice(i + 1); this._progressQueue[i].progress = progress;
return; this._progressQueue[i].status = status;
this._progressQueue = this._progressQueue.slice(0, i + 1);
break;
} }
} }
this._progressQueue.push({progress, status, statusText});
// only dequeue if not already dequeuing
if (this._progressAnimatingTimeout) return;
this.dequeueProgressStatus();
}
setNextProgressStatus() {
if (!this._progressQueue.length) {
// Queue is empty
this._progressAnimatingTimeout = null;
return; return;
} }
// Queue is not empty -> set next progress this.setProgress(progress, status);
this.dequeueProgressStatus();
} }
dequeueProgressStatus() { setNextProgress() {
clearTimeout(this._progressAnimatingTimeout); if (this._progressQueue.length > 0) {
setTimeout(() => {
let {progress, status, statusText} = this._progressQueue.shift(); let next = this._progressQueue.shift()
this.setProgress(next.progress, next.status);
// On complete status: set progress to 0 after 250ms and status to idle after 10s }, 250); // 200 ms animation + buffer
if (this._isCompletedStatus(status)) { }
this._progressQueue.unshift({progress: 0});
this._progressIdleTimeout = setTimeout(() => this.setStatus("idle"), 10000);
} }
// After animation has finished -> set next progress in queue setProgress(progress, status) {
this._progressAnimatingTimeout = setTimeout(() => this.setNextProgressStatus(), 250); // 200 ms animation + buffer this.setStatus(status);
// Only change status if explicitly set
if (status) {
this.setStatus(status, statusText);
}
// Only change progress if explicitly set and differs from current
if (progress === null || progress === this._currentProgress) return;
const progressSpillsOverHalf = this._currentProgress < 0.5 && 0.5 < progress; // 0.5 slips through const progressSpillsOverHalf = this._currentProgress < 0.5 && 0.5 < progress; // 0.5 slips through
const progressSpillsOverFull = progress <= 0.5 && 0.5 <= this._currentProgress && this._currentProgress < 1; const progressSpillsOverFull = progress <= 0.5 && 0.5 <= this._currentProgress && this._currentProgress < 1;
// If spills over half: go to 0.5 first
// If spills over full: go to 1 first
if (progressSpillsOverHalf) { if (progressSpillsOverHalf) {
this._progressQueue.unshift({progress: 0.5}, {progress: progress}); this._progressQueue.unshift({progress: progress, status: status});
this.dequeueProgressStatus(); this.setProgress(0.5, status);
return; return;
} }
else if (progressSpillsOverFull) { else if (progressSpillsOverFull) {
this._progressQueue.unshift({progress: 1}, {progress: progress}); this._progressQueue.unshift({progress: progress, status: status});
this.dequeueProgressStatus(); this.setProgress(1, status);
return; return;
} }
// Clear progress after setting it to 1 if (progress === 0) {
if (progress === 1) { this._currentProgress = 0;
this._progressQueue.unshift({progress: 0}); this.$progress.classList.remove('animate');
} this.$progress.classList.remove('over50');
this.$progress.style.setProperty('--progress', `rotate(${360 * progress}deg)`);
// Set progress to 1 before setting to 0 if not error this.setNextProgress();
if (progress === 0 && this._currentProgress !== 1 && status !== 'error') {
this._progressQueue.unshift({progress: 1});
this.dequeueProgressStatus();
return; return;
} }
// under 0.5 -> remove second circle if (progress < this._currentProgress && status !== this._currentStatus) {
// over 0.5 -> add second circle // reset progress
if (progress < 0.5) { this._progressQueue.unshift({progress: progress, status: status});
this.setProgress(0, status);
return;
}
if (progress === 0) {
this.$progress.classList.remove('animate'); this.$progress.classList.remove('animate');
this.$progress.classList.remove('over50'); this.$progress.classList.remove('over50');
this.$progress.classList.add('animate'); this.$progress.classList.add('animate');
} }
else if (progress > 0.5 && this._currentProgress === 0.5) { else if (this._currentProgress === 0.5) {
this.$progress.classList.remove('animate'); this.$progress.classList.remove('animate');
this.$progress.classList.add('over50'); this.$progress.classList.add('over50');
this.$progress.classList.add('animate'); this.$progress.classList.add('animate');
} }
// Do not animate when setting progress to lower value if (this._currentProgress < progress) {
if (progress < this._currentProgress && this._currentProgress === 1) { this.$progress.classList.add('animate');
}
else {
this.$progress.classList.remove('animate'); this.$progress.classList.remove('animate');
} }
// If document is in background do not animate to prevent flickering on focus
if (!document.hasFocus()) {
this.$progress.classList.remove('animate');
}
this._currentProgress = progress;
this.$progress.style.setProperty('--progress', `rotate(${360 * progress}deg)`); this.$progress.style.setProperty('--progress', `rotate(${360 * progress}deg)`);
this._currentProgress = progress
if (progress === 1) {
// reset progress
this._progressQueue.unshift({progress: 0, status: status});
} }
setStatus(status, statusText = null) { this.setNextProgress();
this._currentStatus = status; }
if (status === 'idle') { setStatus(status) {
if (status === this._currentStatus) return;
clearTimeout(this.statusTimeout);
if (!status) {
this.$el.removeAttribute('status'); this.$el.removeAttribute('status');
this.$el.querySelector('.status').innerText = ''; this.$el.querySelector('.status').innerHTML = '';
this._currentStatus = null;
return; return;
} }
if (!statusText) { let statusName = {
statusText = {
"connect": Localization.getTranslation("peer-ui.connecting"), "connect": Localization.getTranslation("peer-ui.connecting"),
"prepare": Localization.getTranslation("peer-ui.preparing"), "prepare": Localization.getTranslation("peer-ui.preparing"),
"transfer": Localization.getTranslation("peer-ui.transferring"), "transfer": Localization.getTranslation("peer-ui.transferring"),
@@ -820,18 +836,16 @@ class PeerUI {
"receive-complete": Localization.getTranslation("peer-ui.receive-complete"), "receive-complete": Localization.getTranslation("peer-ui.receive-complete"),
"error": Localization.getTranslation("peer-ui.error") "error": Localization.getTranslation("peer-ui.error")
}[status]; }[status];
}
this.$el.setAttribute('status', status); this.$el.setAttribute('status', status);
this.$el.querySelector('.status').innerText = statusText; this.$el.querySelector('.status').innerText = statusName;
} this._currentStatus = status;
_isCompletedStatus(status) { if (["transfer-complete", "receive-complete", "error"].includes(status)) {
return status && (status.endsWith("-complete") || status === "error") this.statusTimeout = setTimeout(() => {
this.setProgress(0, null);
}, 10000);
} }
_isTransferringStatus(status) {
return status !== "connect" && !this._isCompletedStatus(status);
} }
_onDrop(e) { _onDrop(e) {
@@ -839,25 +853,26 @@ class PeerUI {
e.preventDefault(); e.preventDefault();
this._onDragEnd(); if (e.dataTransfer.files.length > 0) {
const peerId = this._peer.id;
const files = e.dataTransfer.files;
const text = e.dataTransfer.getData("text");
if (files.length > 0) {
Events.fire('files-selected', { Events.fire('files-selected', {
files: files, files: e.dataTransfer.files,
to: peerId to: this._peer.id
}); });
} } else {
else if (text.length > 0) { for (let i=0; i<e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].type === "text/plain") {
e.dataTransfer.items[i].getAsString(text => {
Events.fire('send-text', { Events.fire('send-text', {
text: text, text: text,
to: peerId to: this._peer.id
});
}); });
} }
} }
}
this._onDragEnd();
}
_onDragOver() { _onDragOver() {
this.$el.setAttribute('drop', true); this.$el.setAttribute('drop', true);
@@ -1060,7 +1075,7 @@ class ReceiveDialog extends Dialog {
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1}); : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
} }
const fileName = files[0].originalName ?? files[0].name; const fileName = files[0].name;
const fileNameSplit = fileName.split('.'); const fileNameSplit = fileName.split('.');
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
const fileStem = fileName.substring(0, fileName.length - fileExtension.length); const fileStem = fileName.substring(0, fileName.length - fileExtension.length);
@@ -1236,13 +1251,8 @@ class ReceiveFileDialog extends ReceiveDialog {
} }
async _setShareButton() { async _setShareButton() {
// Add original file names
let filesWithOriginalNames = []
for (const file of this._data.files) {
filesWithOriginalNames.push(new File([file], file.originalName, {type: file.type}))
}
this.$shareBtn.onclick = _ => { this.$shareBtn.onclick = _ => {
navigator.share({files: filesWithOriginalNames}) navigator.share({files: this._data.files})
.catch(async err => { .catch(async err => {
Logger.error(err); Logger.error(err);
@@ -1306,6 +1316,8 @@ class ReceiveFileDialog extends ReceiveDialog {
Events.fire('notify-user', downloadSuccessfulTranslation); Events.fire('notify-user', downloadSuccessfulTranslation);
this.downloadSuccessful = true; this.downloadSuccessful = true;
this.hide();
}; };
} }
@@ -1333,6 +1345,8 @@ class ReceiveFileDialog extends ReceiveDialog {
Events.fire('notify-user', downloadSuccessfulTranslation); Events.fire('notify-user', downloadSuccessfulTranslation);
this.downloadSuccessful = true; this.downloadSuccessful = true;
this.hide()
}; };
} }
@@ -1356,7 +1370,7 @@ class ReceiveFileDialog extends ReceiveDialog {
_downloadFiles(files) { _downloadFiles(files) {
let tmpBtn = document.createElement("a"); let tmpBtn = document.createElement("a");
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
tmpBtn.download = files[i].originalName; tmpBtn.download = files[i].name;
tmpBtn.href = URL.createObjectURL(files[i]); tmpBtn.href = URL.createObjectURL(files[i]);
tmpBtn.click(); tmpBtn.click();
} }
@@ -1396,7 +1410,6 @@ class ReceiveFileDialog extends ReceiveDialog {
this.$downloadBtn.removeAttribute('disabled'); this.$downloadBtn.removeAttribute('disabled');
this.$downloadBtn.removeAttribute('hidden'); this.$downloadBtn.removeAttribute('hidden');
// Todo: complete rewrite of the download button logic and file zipping
let {sendAsZip, zipObjectUrl, zipName} = await this._processDataAsZip(); let {sendAsZip, zipObjectUrl, zipName} = await this._processDataAsZip();
// If single file or zipping failed or file size exceeds memory -> download files individually -> else download zip // If single file or zipping failed or file size exceeds memory -> download files individually -> else download zip
@@ -1435,27 +1448,12 @@ class ReceiveFileDialog extends ReceiveDialog {
this.$previewBox.innerHTML = ''; this.$previewBox.innerHTML = '';
} }
async _deleteFilesFromDisk(files) {
for(const file of files) {
if (!file.id) {
continue;
}
let fileId = await SWFileDigester.deleteFileById(file.id)
Logger.log(`File with id: ${fileId} successfully deleted.`)
}
}
hide() { hide() {
super.hide(); super.hide();
setTimeout(async () => { setTimeout(async () => {
this._tidyUpButtons(); this._tidyUpButtons();
this._tidyUpPreviewBox(); this._tidyUpPreviewBox();
let oldFiles = this._data.files;
this._deleteFilesFromDisk(oldFiles)
.then(() => Logger.log("Deletion of old files finished."))
this._busy = false; this._busy = false;
await this._processFiles(); await this._processFiles();
@@ -2079,8 +2077,8 @@ class PublicRoomDialog extends Dialog {
Events.on('keydown', e => this._onKeyDown(e)); Events.on('keydown', e => this._onKeyDown(e));
Events.on('public-room-created', e => this._onPublicRoomCreated(e.detail)); Events.on('public-room-created', e => this._onPublicRoomCreated(e.detail));
Events.on('peers', e => this._onPeers(e.detail.peers, e.detail.roomId)); Events.on('peers', e => this._onPeers(e.detail));
Events.on('peer-joined', e => this._onPeerJoined(e.detail.roomId)); Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomId));
Events.on('public-room-id-invalid', e => this._onPublicRoomIdInvalid(e.detail)); Events.on('public-room-id-invalid', e => this._onPublicRoomIdInvalid(e.detail));
Events.on('public-room-left', _ => this._onPublicRoomLeft()); Events.on('public-room-left', _ => this._onPublicRoomLeft());
this.$el.addEventListener('paste', e => this._onPaste(e)); this.$el.addEventListener('paste', e => this._onPaste(e));
@@ -2210,30 +2208,29 @@ class PublicRoomDialog extends Dialog {
} }
} }
_onPeers(peers, roomId) { _onPeers(message) {
// Do not evaluate if creating new room message.peers.forEach(messagePeer => {
if (this.roomId && !peers.length) return; this._evaluateJoinedPeer(messagePeer.id, message.roomId);
});
this._evaluateJoinedPeer(roomId);
} }
_onPeerJoined(roomId) { _onPeerJoined(peer, roomId) {
this._evaluateJoinedPeer(roomId); this._evaluateJoinedPeer(peer.id, roomId);
} }
_evaluateJoinedPeer(roomId) { _evaluateJoinedPeer(peerId, roomId) {
const peerJoinedThisRoom = roomId === this.roomId; const isInitiatedRoomId = roomId === this.roomId;
const switchedToOtherRoom = roomId === this.roomIdJoin; const isJoinedRoomId = roomId === this.roomIdJoin;
if (!roomId || (!peerJoinedThisRoom && !switchedToOtherRoom)) return; if (!peerId || !roomId || (!isInitiatedRoomId && !isJoinedRoomId)) return;
this.hide(); this.hide();
sessionStorage.setItem('public_room_id', roomId); sessionStorage.setItem('public_room_id', roomId);
if (switchedToOtherRoom) { if (isJoinedRoomId) {
this.roomIdJoin = null;
this.roomId = roomId; this.roomId = roomId;
this.roomIdJoin = false;
this._setKeyAndQrCode(); this._setKeyAndQrCode();
} }
} }
@@ -2246,7 +2243,7 @@ class PublicRoomDialog extends Dialog {
} }
_leavePublicRoom() { _leavePublicRoom() {
Events.fire('leave-public-room'); Events.fire('leave-public-room', this.roomId);
} }
_onPublicRoomLeft() { _onPublicRoomLeft() {
@@ -2285,8 +2282,6 @@ class SendTextDialog extends Dialog {
this.$submit = this.$el.querySelector('button[type="submit"]'); this.$submit = this.$el.querySelector('button[type="submit"]');
this.$form.addEventListener('submit', e => this._onSubmit(e)); this.$form.addEventListener('submit', e => this._onSubmit(e));
this.$text.addEventListener('input', _ => this._onInput()); this.$text.addEventListener('input', _ => this._onInput());
this.$text.addEventListener('paste', e => this._onPaste(e));
this.$text.addEventListener('drop', e => this._onDrop(e));
Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName)); Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
Events.on('keydown', e => this._onKeyDown(e)); Events.on('keydown', e => this._onKeyDown(e));
@@ -2305,40 +2300,6 @@ class SendTextDialog extends Dialog {
} }
} }
async _onDrop(e) {
e.preventDefault()
const text = e.dataTransfer.getData("text");
const selection = window.getSelection();
if (selection.rangeCount) {
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(text));
}
this._onInput();
}
async _onPaste(e) {
e.preventDefault()
const text = (e.clipboardData || window.clipboardData).getData('text');
const selection = window.getSelection();
if (selection.rangeCount) {
selection.deleteFromDocument();
const textNode = document.createTextNode(text);
const range = document.createRange();
range.setStart(textNode, textNode.length);
range.collapse(true);
selection.getRangeAt(0).insertNode(textNode);
selection.removeAllRanges();
selection.addRange(range);
}
this._onInput();
}
_textEmpty() { _textEmpty() {
return !this.$text.innerText || this.$text.innerText === "\n"; return !this.$text.innerText || this.$text.innerText === "\n";
} }
@@ -2401,7 +2362,6 @@ class ReceiveTextDialog extends Dialog {
this.$displayName = this.$el.querySelector('.display-name'); this.$displayName = this.$el.querySelector('.display-name');
this._receiveTextQueue = []; this._receiveTextQueue = [];
this._hideTimeout = null;
} }
selectionEmpty() { selectionEmpty() {
@@ -2423,17 +2383,12 @@ class ReceiveTextDialog extends Dialog {
audioPlayer.playBlop(); audioPlayer.playBlop();
this._receiveTextQueue.push({text: text, peerId: peerId}); this._receiveTextQueue.push({text: text, peerId: peerId});
this._setDocumentTitleMessages(); this._setDocumentTitleMessages();
changeFavicon("images/favicon-96x96-notification.png"); if (this.isShown()) return;
if (this.isShown() || this._hideTimeout) return;
this._dequeueRequests(); this._dequeueRequests();
} }
_dequeueRequests() { _dequeueRequests() {
this._setDocumentTitleMessages(); if (!this._receiveTextQueue.length) return;
changeFavicon("images/favicon-96x96-notification.png");
let {text, peerId} = this._receiveTextQueue.shift(); let {text, peerId} = this._receiveTextQueue.shift();
this._showReceiveTextDialog(text, peerId); this._showReceiveTextDialog(text, peerId);
} }
@@ -2444,68 +2399,41 @@ class ReceiveTextDialog extends Dialog {
this.$displayName.classList.add($(peerId).ui._badgeClassName()); this.$displayName.classList.add($(peerId).ui._badgeClassName());
this.$text.innerText = text; this.$text.innerText = text;
this.$text.classList.remove('text-center');
// Beautify text if text is not too long // Beautify text if text is short
if (this.$text.innerText.length <= 300000) { if (text.length < 2000) {
// Hacky workaround to replace URLs with link nodes in all cases // replace URLs with actual links
// 1. Use text variable, find all valid URLs via regex and replace URLs with placeholder this.$text.innerHTML = this.$text.innerHTML
// 2. Use html variable, find placeholders with regex and replace them with link nodes .replace(/(^|<br>|\s|")((https?:\/\/|www.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%]){2,}\.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%.]){2,}))/g,
(match, whitespace, url) => {
let $textShadow = document.createElement('div');
$textShadow.innerText = text;
let linkNodes = {};
let searchHTML = $textShadow.innerHTML;
const p = "@";
const pRgx = new RegExp(`${p}\\d+`, 'g');
let occP = searchHTML.match(pRgx) || [];
let m = 0;
const allowedDomainChars = "a-zA-Z0-9áàäčçđéèêŋńñóòôöšŧüžæøåëìíîïðùúýþćěłřśţźǎǐǒǔǥǧǩǯəʒâûœÿãõāēīōūăąĉċďĕėęĝğġģĥħĩĭįıĵķĸĺļľņňŏőŕŗŝşťũŭůűųŵŷżאבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ";
const urlRgx = new RegExp(`(^|\\n|\\s|["><\\-_~:\\/?#\\[\\]@!$&'()*+,;=%.])(((https?:\\/\\/)?(?:[${allowedDomainChars}](?:[${allowedDomainChars}-]{0,61}[${allowedDomainChars}])?\\.)+[${allowedDomainChars}][${allowedDomainChars}-]{0,61}[${allowedDomainChars}])(:?\\d*)\\/?([${allowedDomainChars}_\\/\\-#.]*)(\\?([${allowedDomainChars}\\-_~:\\/?#\\[\\]@!$&'()*+,;=%.]*))?)`, 'g');
$textShadow.innerText = text.replace(urlRgx,
(match, whitespaceOrSpecial, url, g3, scheme) => {
let link = url; let link = url;
// prefix www.example.com with http protocol to prevent it from being a relative link // prefix www.example.com with http protocol to prevent it from being a relative link
if (!scheme && link.startsWith('www')) { if (link.startsWith('www')) {
link = "http://" + link link = "http://" + link
} }
// Check if link is valid
if (isUrlValid(link)) { if (isUrlValid(link)) {
// link is valid -> replace with link node placeholder return `${whitespace}<a href="${link}" target="_blank">${url}</a>`;
// find linkNodePlaceholder that is not yet present in text node
m++;
while (occP.includes(`${p}${m}`)) {
m++;
} }
let linkNodePlaceholder = `${p}${m}`; else {
// add linkNodePlaceholder to text node and save a reference to linkNodes object
linkNodes[linkNodePlaceholder] = `<a href="${link}" target="_blank">${url}</a>`;
return `${whitespaceOrSpecial}${linkNodePlaceholder}`;
}
// link is not valid -> do not replace
return match; return match;
}); }
this.$text.innerHTML = $textShadow.innerHTML.replace(pRgx,
(m) => {
let urlNode = linkNodes[m];
return urlNode ? urlNode : m;
}); });
} }
this._evaluateOverflowing(this.$text); this._evaluateOverflowing(this.$text);
this._setDocumentTitleMessages();
changeFavicon("images/favicon-96x96-notification.png");
this.show(); this.show();
} }
_setDocumentTitleMessages() { _setDocumentTitleMessages() {
document.title = this._receiveTextQueue.length <= 1 document.title = !this._receiveTextQueue.length
? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop` ? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
: `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`; : `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
} }
@@ -2525,16 +2453,9 @@ class ReceiveTextDialog extends Dialog {
hide() { hide() {
super.hide(); super.hide();
setTimeout(() => {
// If queue is empty -> clear text field | else -> open next message
this._hideTimeout = setTimeout(() => {
if (!this._receiveTextQueue.length) {
this.$text.innerHTML = "";
}
else {
this._dequeueRequests(); this._dequeueRequests();
} this.$text.innerHTML = "";
this._hideTimeout = null;
}, 500); }, 500);
} }
} }
@@ -2669,20 +2590,15 @@ class Base64Dialog extends Dialog {
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing"); this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing");
} }
preparePasting(type, useFallback = false) { preparePasting(type) {
const translateType = type === 'text' const translateType = type === 'text'
? Localization.getTranslation("dialogs.base64-text") ? Localization.getTranslation("dialogs.base64-text")
: Localization.getTranslation("dialogs.base64-files"); : Localization.getTranslation("dialogs.base64-files");
if (navigator.clipboard.readText && !useFallback) { if (navigator.clipboard.readText) {
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType}); this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType});
this._clickCallback = _ => this.processClipboard(type); this._clickCallback = _ => this.processClipboard(type);
this.$pasteBtn.addEventListener('click', _ => { this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
this._clickCallback()
.catch(_ => {
this.preparePasting(type, true);
})
});
} }
else { else {
Logger.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") Logger.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.")
-23
View File
@@ -620,26 +620,3 @@ function isUrlValid(url) {
return false; return false;
} }
} }
// polyfill for crypto.randomUUID()
// Credits: @Briguy37 - https://stackoverflow.com/a/8809472/14678591
function generateUUID() {
return crypto && crypto.randomUUID()
? crypto.randomUUID()
: () => {
let
d = new Date().getTime(),
d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
let r = Math.random() * 16;
if (d > 0) {
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
});
};
}
+2 -8
View File
@@ -1,4 +1,4 @@
const cacheVersion = 'v1.10.9'; const cacheVersion = 'v1.10.6';
const cacheTitle = `pairdrop-cache-${cacheVersion}`; const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions
const relativePathsToCache = [ const relativePathsToCache = [
@@ -27,28 +27,22 @@ const relativePathsToCache = [
'images/android-chrome-512x512-maskable.png', 'images/android-chrome-512x512-maskable.png',
'images/apple-touch-icon.png', 'images/apple-touch-icon.png',
'lang/ar.json', 'lang/ar.json',
'lang/be.json',
'lang/ca.json', 'lang/ca.json',
'lang/da.json',
'lang/de.json', 'lang/de.json',
'lang/en.json', 'lang/en.json',
'lang/es.json', 'lang/es.json',
'lang/fr.json', 'lang/fr.json',
'lang/he.json',
'lang/hu.json',
'lang/id.json', 'lang/id.json',
'lang/it.json', 'lang/it.json',
'lang/ja.json', 'lang/ja.json',
'lang/kn.json', 'lang/kn.json',
'lang/nb.json', 'lang/nb.json',
'lang/nl.json', 'lang/nl.json',
'lang/pl.json',
'lang/pt-BR.json', 'lang/pt-BR.json',
'lang/ro.json', 'lang/ro.json',
'lang/ru.json', 'lang/ru.json',
'lang/tr.json', 'lang/tr.json',
'lang/zh-CN.json', 'lang/zh-CN.json'
'lang/zh-TW.json'
]; ];
const relativePathsNotToCache = [ const relativePathsNotToCache = [
'config' 'config'
+36 -1
View File
@@ -12,6 +12,7 @@
display: block; display: block;
overflow: auto; overflow: auto;
resize: none; resize: none;
line-height: 16px;
max-height: 350px; max-height: 350px;
word-break: break-word; word-break: break-word;
word-wrap: anywhere; word-wrap: anywhere;
@@ -278,6 +279,40 @@ x-peer[drop] x-icon {
transform: scale(1.1); transform: scale(1.1);
} }
x-peer:not(.turn) .turn-indicator-wrapper {
display: none;
}
x-peer .turn-indicator-wrapper {
z-index: 0;
display: flex;
position: absolute;
top: 43px;
right: 23px;
width: 22px;
height: 22px;
}
x-peer .turn-indicator-wrapper:before {
z-index: -1;
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: white;
border-radius: 50%;
border: solid 2px var(--primary-color);
}
x-peer .turn-indicator {
margin: auto;
width: calc(var(--icon-size) / 3);
height: calc(var(--icon-size) / 3);
fill: var(--primary-color);
}
/* Checkboxes as slider */ /* Checkboxes as slider */
.switch { .switch {
@@ -768,7 +803,7 @@ x-dialog .dialog-subheader {
} }
.animate .circle { .animate .circle {
transition: transform 200ms linear; transition: transform 200ms;
} }
.over50 { .over50 {
+1 -4
View File
@@ -626,15 +626,12 @@ x-dialog:not([show]) x-background {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: -1;
opacity: 0; opacity: 0;
background-color: var(--accent-color); background-color: var(--accent-color);
transition: opacity 300ms; transition: opacity 300ms;
} }
.icon-button:before {
z-index: -1;
}
.btn:not([disabled]):hover:before, .btn:not([disabled]):hover:before,
.icon-button:hover:before { .icon-button:hover:before {
opacity: 0.3; opacity: 0.3;
+4 -7
View File
@@ -44,18 +44,15 @@ export default class Peer {
_setIP(request) { _setIP(request) {
if (request.headers['cf-connecting-ip']) { if (request.headers['cf-connecting-ip']) {
this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0]; this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0];
} } else if (request.headers['x-forwarded-for']) {
else if (request.headers['x-forwarded-for']) {
this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
} } else {
else { this.ip = request.connection.remoteAddress;
this.ip = request.socket.remoteAddress ?? '';
} }
// remove the prefix used for IPv4-translated addresses // remove the prefix used for IPv4-translated addresses
if (this.ip.substring(0,7) === "::ffff:") { if (this.ip.substring(0,7) === "::ffff:")
this.ip = this.ip.substring(7); this.ip = this.ip.substring(7);
}
let ipv6_was_localized = false; let ipv6_was_localized = false;
if (this.conf.ipv6Localize && this.ip.includes(':')) { if (this.conf.ipv6Localize && this.ip.includes(':')) {
+2 -1
View File
@@ -251,6 +251,7 @@ export default class PairDropWsServer {
return; return;
} }
this._leavePublicRoom(sender);
this._joinPublicRoom(sender, message.publicRoomId); this._joinPublicRoom(sender, message.publicRoomId);
} }
@@ -311,7 +312,7 @@ export default class PairDropWsServer {
_joinPublicRoom(peer, publicRoomId) { _joinPublicRoom(peer, publicRoomId) {
// prevent joining of 2 public rooms simultaneously // prevent joining of 2 public rooms simultaneously
this._leavePublicRoom(peer, true); this._leavePublicRoom(peer);
this._joinRoom(peer, 'public-id', publicRoomId); this._joinRoom(peer, 'public-id', publicRoomId);