mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-21 07:16:18 -04:00
implement localization
This commit is contained in:
parent
29b91cb17a
commit
f50d7438b6
12 changed files with 883 additions and 286 deletions
|
@ -39,62 +39,66 @@
|
||||||
|
|
||||||
<body translate="no">
|
<body translate="no">
|
||||||
<header class="row-reverse">
|
<header class="row-reverse">
|
||||||
<a href="#about" class="icon-button" title="About PairDrop" aria-label="Open About PairDrop">
|
<a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label" title="About PairDrop" aria-label="Open About PairDrop">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#info-outline" />
|
<use xlink:href="#info-outline" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div id="theme-wrapper">
|
<div id="theme-wrapper">
|
||||||
<div id="theme-auto" class="icon-button selected" title="Adapt Theme to System" >
|
<div id="theme-auto" class="icon-button selected" data-i18n-key="header.theme-auto" data-i18n-attrs="title" title="Adapt Theme to System" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-auto" />
|
<use xlink:href="#icon-theme-auto" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div id="theme-light" class="icon-button" title="Always Use Light-Theme" >
|
<div id="theme-light" class="icon-button" data-i18n-key="header.theme-light" data-i18n-attrs="title" title="Always Use Light-Theme" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-light" />
|
<use xlink:href="#icon-theme-light" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="theme-dark" class="icon-button" title="Always Use Dark-Theme" >
|
<div id="theme-dark" class="icon-button" data-i18n-key="header.theme-dark" data-i18n-attrs="title" title="Always Use Dark-Theme" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-dark" />
|
<use xlink:href="#icon-theme-dark" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification" class="icon-button" title="Enable Notifications" hidden>
|
<div id="notification" class="icon-button" data-i18n-key="header.notification" data-i18n-attrs="title" title="Enable Notifications" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#notifications" />
|
<use xlink:href="#notifications" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="install" class="icon-button" title="Install PairDrop" hidden>
|
<div id="install" class="icon-button" data-i18n-key="header.install" data-i18n-attrs="title" title="Install PairDrop" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#homescreen" />
|
<use xlink:href="#homescreen" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="pair-device" class="icon-button" title="Pair Device" hidden>
|
<div id="pair-device" class="icon-button" data-i18n-key="header.pair-device" data-i18n-attrs="title" title="Pair Device" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#pair-device-icon" />
|
<use xlink:href="#pair-device-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="edit-paired-devices" class="icon-button" title="Edit Paired Devices" hidden>
|
<div id="edit-paired-devices" class="icon-button" data-i18n-key="header.edit-paired-devices" data-i18n-attrs="title" title="Edit Paired Devices" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#edit-pair-devices-icon" />
|
<use xlink:href="#edit-pair-devices-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="cancel-paste-mode" class="button" hidden>Done</div>
|
<div id="cancel-paste-mode" class="button" data-i18n-key="header.done" data-i18n-attrs="text" hidden>Done</div>
|
||||||
</header>
|
</header>
|
||||||
<!-- Center -->
|
<!-- Center -->
|
||||||
<div id="center">
|
<div id="center">
|
||||||
<!-- Peers -->
|
<!-- Peers -->
|
||||||
<div class="x-peers-filler"></div>
|
<div class="x-peers-filler"></div>
|
||||||
<x-peers class="center"></x-peers>
|
<x-peers class="center"></x-peers>
|
||||||
<x-no-peers>
|
<x-no-peers data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg" data-drop-bg="Release to select recipient">
|
||||||
<h2>Open PairDrop on other devices to send files</h2>
|
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text">Open PairDrop on other devices to send files</h2>
|
||||||
<div>Pair devices to be discoverable on other networks</div>
|
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text">Pair devices to be discoverable on other networks</div>
|
||||||
</x-no-peers>
|
</x-no-peers>
|
||||||
<x-instructions desktop="Click to send files or right click to send a message" mobile="Tap to send files or long tap to send a message">
|
<x-instructions data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg"
|
||||||
|
desktop="Click to send files or right click to send a message"
|
||||||
|
mobile="Tap to send files or long tap to send a message"
|
||||||
|
data-drop-peer="Release to send to peer"
|
||||||
|
data-drop-bg="Release to select recipient">
|
||||||
<p id="paste-filename"></p>
|
<p id="paste-filename"></p>
|
||||||
</x-instructions>
|
</x-instructions>
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,15 +108,21 @@
|
||||||
<use xlink:href="#wifi-tethering" />
|
<use xlink:href="#wifi-tethering" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<span>You are known as:</span>
|
<span data-i18n-key="footer.known-as" data-i18n-attrs="text">You are known as:</span>
|
||||||
<div id="display-name" placeholder="Loading..." title="Edit your device name permanently" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div>
|
<div id="display-name" data-i18n-key="footer.display-name" data-i18n-attrs="placeholder title" placeholder="Loading..." title="Edit your device name permanently" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div>
|
||||||
<svg id="edit-pen" class="icon">
|
<svg id="edit-pen" class="icon">
|
||||||
<use xlink:href="#edit-pen-icon" />
|
<use xlink:href="#edit-pen-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-body2">
|
<div class="font-body2">
|
||||||
You can be discovered by everyone <span id="on-this-network">on this network</span>
|
<div>
|
||||||
<span id="and-by-paired-devices" hidden> and by <span id="paired-devices">paired devices</span></span>
|
<span data-i18n-key="footer.discovery-everyone" data-i18n-attrs="text">You can be discovered by everyone</span>
|
||||||
|
<span id="on-this-network" data-i18n-key="footer.on-this-network" data-i18n-attrs="text">on this network</span>
|
||||||
|
</div>
|
||||||
|
<div id="and-by-paired-devices" hidden>
|
||||||
|
<span id="and-by" data-i18n-key="footer.and-by" data-i18n-attrs="text">and by</span>
|
||||||
|
<span id="paired-devices" data-i18n-key="footer.paired-devices" data-i18n-attrs="text">paired devices</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<!-- Pair Device Dialog -->
|
<!-- Pair Device Dialog -->
|
||||||
|
@ -120,10 +130,13 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center text-center">
|
<x-background class="full center text-center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="center">Pair Devices</h2>
|
<h2 class="center" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text">Pair Devices</h2>
|
||||||
<div id="room-key-qr-code" class="center"></div>
|
<div id="room-key-qr-code" class="center"></div>
|
||||||
<h1 id="room-key" class="center">000 000</h1>
|
<h1 id="room-key" class="center">000 000</h1>
|
||||||
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
|
<div id="pair-instructions" class="center text-center">
|
||||||
|
<span class="font-subheading" data-i18n-key="dialogs.input-key-on-this-device" data-i18n-attrs="text">Input this key on another device</span>
|
||||||
|
<span class="font-subheading" data-i18n-key="dialogs.scan-qr-code" data-i18n-attrs="text">or scan the QR-Code.</span>
|
||||||
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div id="key-input-container">
|
<div id="key-input-container">
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||||
|
@ -133,10 +146,10 @@
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-subheading center text-center">Enter key from another device to continue.</div>
|
<div class="font-subheading center text-center" data-i18n-key="dialogs.enter-key-from-another-device" data-i18n-attrs="text">Enter key from another device to continue.</div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" disabled>Pair</button>
|
<button class="button" type="submit" data-i18n-key="dialogs.pair" data-i18n-attrs="text" disabled>Pair</button>
|
||||||
<button class="button" type="button" close>Cancel</button>
|
<button class="button" type="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -147,13 +160,21 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center text-center">
|
<x-background class="full center text-center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="center">Edit Paired Devices</h2>
|
<h2 class="center" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text">Edit Paired Devices</h2>
|
||||||
<div class="paired-devices-wrapper"></div>
|
<div class="paired-devices-wrapper" data-i18n-key="dialogs.paired-devices-empty" data-i18n-attrs="data-empty" data-empty="No paired devices."></div>
|
||||||
<div class="font-subheading center">
|
<div class="font-subheading center">
|
||||||
<p>Activate <u>auto-accept</u> to automatically accept all files sent from that device.</p>
|
<p>
|
||||||
|
<span data-i18n-key="dialogs.auto-accept-instructions-1" data-i18n-attrs="text">
|
||||||
|
Activate
|
||||||
|
</span>
|
||||||
|
<u data-i18n-key="dialogs.auto-accept" data-i18n-attrs="text">auto-accept</u>
|
||||||
|
<span data-i18n-key="dialogs.auto-accept-instructions-2" data-i18n-attrs="text">
|
||||||
|
to automatically accept all files sent from that device.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="button" close>Close</button>
|
<button class="button" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -167,7 +188,7 @@
|
||||||
<div class="center column file-description">
|
<div class="center column file-description">
|
||||||
<div>
|
<div>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>would like to share</span>
|
<span data-i18n-key="dialogs.would-like-to-share" data-i18n-attrs="text">would like to share</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row file-name" >
|
<div class="row file-name" >
|
||||||
<span class="file-stem"></span>
|
<span class="file-stem"></span>
|
||||||
|
@ -179,8 +200,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="center file-preview"></div>
|
<div class="center file-preview"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="accept-request" class="button" title="ENTER" autofocus>Accept</button>
|
<button id="accept-request" class="button" title="ENTER" data-i18n-key="dialogs.accept" data-i18n-attrs="text" autofocus>Accept</button>
|
||||||
<button id="decline-request" class="button" title="ESCAPE">Decline</button>
|
<button id="decline-request" class="button" title="ESCAPE" data-i18n-key="dialogs.decline" data-i18n-attrs="text">Decline</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -193,7 +214,7 @@
|
||||||
<div class="center column file-description">
|
<div class="center column file-description">
|
||||||
<div>
|
<div>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>has sent</span>
|
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text">has sent</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row file-name" >
|
<div class="row file-name" >
|
||||||
<span class="file-stem"></span>
|
<span class="file-stem"></span>
|
||||||
|
@ -204,9 +225,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="center file-preview"></div>
|
<div class="center file-preview"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="share-btn" class="button" autofocus hidden>Share</button>
|
<button id="share-btn" class="button" data-i18n-key="dialogs.share" data-i18n-attrs="text" autofocus hidden>Share</button>
|
||||||
<button id="download-btn" class="button" autofocus>Download</button>
|
<button id="download-btn" class="button" data-i18n-key="dialogs.download" data-i18n-attrs="text" autofocus>Download</button>
|
||||||
<button class="button" close>Close</button>
|
<button class="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -216,16 +237,16 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="text-center">Send Message</h2>
|
<h2 class="text-center" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text">Send Message</h2>
|
||||||
<div class="dialog-subheader text-center">
|
<div class="dialog-subheader text-center">
|
||||||
<span>Send a Message to</span>
|
<span data-i18n-key="dialogs.send-message-to" data-i18n-attrs="text">Send a Message to</span>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
|
<button class="button" type="submit" title="CTRL/⌘ + ENTER" data-i18n-key="dialogs.send" data-i18n-attrs="text" disabled>Send</button>
|
||||||
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
<button class="button" type="button" title="ESCAPE" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -235,16 +256,16 @@
|
||||||
<x-dialog id="receive-text-dialog">
|
<x-dialog id="receive-text-dialog">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="text-center">Message Received</h2>
|
<h2 class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text">Message Received</h2>
|
||||||
<div class="text-center dialog-subheader">
|
<div class="text-center dialog-subheader">
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>has sent:</span>
|
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text">has sent:</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text"></div>
|
<div id="text"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="copy" class="button" title="CTRL/⌘ + C">Copy</button>
|
<button id="copy" class="button" title="CTRL/⌘ + C" data-i18n-key="dialogs.copy" data-i18n-attrs="text">Copy</button>
|
||||||
<button id="close" class="button" title="ESCAPE">Close</button>
|
<button id="close" class="button" title="ESCAPE" data-i18n-key="dialogs.close" data-i18n-attrs="text">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -253,9 +274,9 @@
|
||||||
<x-dialog id="base64-paste-dialog">
|
<x-dialog id="base64-paste-dialog">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<button class="button center" id="base64-paste-btn" title="Paste">Tap here to paste files</button>
|
<button class="button center" id="base64-paste-btn" title="Paste"></button>
|
||||||
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
||||||
<button class="button center" close>Close</button>
|
<button class="button center" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
</x-dialog>
|
</x-dialog>
|
||||||
|
@ -266,7 +287,7 @@
|
||||||
<!-- About Page -->
|
<!-- About Page -->
|
||||||
<x-about id="about" class="full center column">
|
<x-about id="about" class="full center column">
|
||||||
<header class="row-reverse fade-in">
|
<header class="row-reverse fade-in">
|
||||||
<a href="#" class="close icon-button" aria-label="Close About PairDrop">
|
<a href="#" class="close icon-button" data-i18n-key="about.close-about" data-i18n-attrs="text" aria-label="Close About PairDrop">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#close-icon" />
|
<use xlink:href="#close-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -280,7 +301,7 @@
|
||||||
<h1>PairDrop</h1>
|
<h1>PairDrop</h1>
|
||||||
<div class="font-subheading">v1.7.6</div>
|
<div class="font-subheading">v1.7.6</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
<div class="font-subheading" data-i18n-key="about.claim" data-i18n-attrs="text">The easiest way to transfer files across devices</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
|
@ -373,6 +394,7 @@
|
||||||
</symbol>
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
|
<script src="scripts/localization.js"></script>
|
||||||
<script src="scripts/theme.js"></script>
|
<script src="scripts/theme.js"></script>
|
||||||
<script src="scripts/network.js"></script>
|
<script src="scripts/network.js"></script>
|
||||||
<script src="scripts/ui.js"></script>
|
<script src="scripts/ui.js"></script>
|
||||||
|
|
136
public/lang/en.json
Normal file
136
public/lang/en.json
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"about_title": "About PairDrop",
|
||||||
|
"about_aria-label": "Open About PairDrop",
|
||||||
|
"theme-auto_title": "Adapt Theme to System",
|
||||||
|
"theme-light_title": "Always Use Light-Theme",
|
||||||
|
"theme-dark_title": "Always Use Dark-Theme",
|
||||||
|
"notification_title": "Enable Notifications",
|
||||||
|
"install_title": "Install PairDrop",
|
||||||
|
"pair-device_title": "Pair Device",
|
||||||
|
"edit-paired-devices_title": "Edit Paired Devices",
|
||||||
|
"cancel-paste-mode": "Done"
|
||||||
|
},
|
||||||
|
"instructions": {
|
||||||
|
"no-peers_data-drop-bg": "Release to select recipient",
|
||||||
|
"no-peers-title": "Open PairDrop on other devices to send files",
|
||||||
|
"no-peers-subtitle": "Pair devices to be discoverable on other networks",
|
||||||
|
"x-instructions_desktop": "Click to send files or right click to send a message",
|
||||||
|
"x-instructions_mobile": "Tap to send files or long tap to send a message",
|
||||||
|
"x-instructions_data-drop-peer": "Release to send to peer",
|
||||||
|
"x-instructions_data-drop-bg": "Release to select recipient",
|
||||||
|
"click-to-send": "Click to send",
|
||||||
|
"tap-to-send": "Tap to send"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"known-as": "You are known as:",
|
||||||
|
"display-name_placeholder": "Loading...",
|
||||||
|
"display-name_title": "Edit your device name permanently",
|
||||||
|
"discovery-everyone": "You can be discovered by everyone",
|
||||||
|
"on-this-network": "on this network",
|
||||||
|
"and-by": "and by",
|
||||||
|
"paired-devices": "paired devices",
|
||||||
|
"traffic": "Traffic is",
|
||||||
|
"routed": "routed through the server",
|
||||||
|
"webrtc": "if WebRTC is not available."
|
||||||
|
},
|
||||||
|
"dialogs": {
|
||||||
|
"activate-paste-mode-base": "Open PairDrop on other devices to send",
|
||||||
|
"activate-paste-mode-and-other-files": "and {{count}} other files",
|
||||||
|
"activate-paste-mode-activate-paste-mode-shared-text": "shared text",
|
||||||
|
"pair-devices-title": "Pair Devices",
|
||||||
|
"input-key-on-this-device": "Input this key on another device",
|
||||||
|
"scan-qr-code": "or scan the QR-Code.",
|
||||||
|
"enter-key-from-another-device": "Enter key from another device to continue.",
|
||||||
|
"pair": "Pair",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"edit-paired-devices-title": "Edit Paired Devices",
|
||||||
|
"paired-devices-wrapper_data-empty": "No paired devices.",
|
||||||
|
"auto-accept-instructions-1": "Activate",
|
||||||
|
"auto-accept": "auto-accept",
|
||||||
|
"auto-accept-instructions-2": "to automatically accept all files sent from that device.",
|
||||||
|
"close": "Close",
|
||||||
|
"would-like-to-share": "would like to share",
|
||||||
|
"accept": "Accept",
|
||||||
|
"decline": "Decline",
|
||||||
|
"has-sent": "has sent:",
|
||||||
|
"share": "Share",
|
||||||
|
"download": "Download",
|
||||||
|
"send-message-title": "Send Message",
|
||||||
|
"send-message-to": "Send a Message to",
|
||||||
|
"send": "Send",
|
||||||
|
"receive-text-title": "Message Received",
|
||||||
|
"copy": "Copy",
|
||||||
|
"base64-processing": "Processing...",
|
||||||
|
"base64-tap-to-paste": "Tap here to paste {{type}}",
|
||||||
|
"base64-paste-to-send": "Paste here to send {{type}}",
|
||||||
|
"base64-text": "text",
|
||||||
|
"base64-files": "files",
|
||||||
|
"file-other-description-image": "and 1 other image",
|
||||||
|
"file-other-description-file": "and 1 other file",
|
||||||
|
"file-other-description-image-plural": "and {{count}} other images",
|
||||||
|
"file-other-description-file-plural": "and {{count}} other files",
|
||||||
|
"title-image": "Image",
|
||||||
|
"title-file": "File",
|
||||||
|
"title-image-plural": "Images",
|
||||||
|
"title-file-plural": "Files",
|
||||||
|
"receive-title": "{{descriptor}} Received",
|
||||||
|
"download-again": "Download again"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"close-about-aria-label": "Close About PairDrop",
|
||||||
|
"claim": "The easiest way to transfer files across devices"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"display-name-changed-permanently": "Display name is changed permanently.",
|
||||||
|
"display-name-changed-temporarily": "Display name is changed only for this session.",
|
||||||
|
"display-name-random-again": "Display name is randomly generated again.",
|
||||||
|
"download-successful": "{{descriptor}} downloaded successfully",
|
||||||
|
"pairing-tabs-error": "Pairing of two browser tabs is not possible.",
|
||||||
|
"pairing-success": "Devices paired successfully.",
|
||||||
|
"pairing-not-persistent": "Paired devices are not persistent.",
|
||||||
|
"pairing-key-invalid": "Key not valid",
|
||||||
|
"pairing-key-invalidated": "Key {{key}} invalidated.",
|
||||||
|
"pairing-cleared": "All Devices unpaired.",
|
||||||
|
"copied-to-clipboard": "Copied to clipboard",
|
||||||
|
"text-content-incorrect": "Text content is incorrect.",
|
||||||
|
"file-content-incorrect": "File content is incorrect.",
|
||||||
|
"clipboard-content-incorrect": "Clipboard content is incorrect.",
|
||||||
|
"notifications-enabled": "Notifications enabled.",
|
||||||
|
"link-received": "Link received by {{name}} - Click to open",
|
||||||
|
"message-received": "Message received by {{name}} - Click to copy",
|
||||||
|
"click-to-download": "Click to download",
|
||||||
|
"request-title": "{{name}} would like to transfer {{count}} {{descriptor}}",
|
||||||
|
"click-to-show": "Click to show",
|
||||||
|
"copied-text": "Copied text to clipboard",
|
||||||
|
"copied-text-error": "Writing to clipboard failed. Copy manually!",
|
||||||
|
"offline": "You are offline",
|
||||||
|
"online": "You are back online",
|
||||||
|
"connected": "Connected.",
|
||||||
|
"online-requirement": "You need to be online to pair devices.",
|
||||||
|
"connecting": "Connecting...",
|
||||||
|
"files-incorrect": "Files are incorrect.",
|
||||||
|
"file-transfer-completed": "File transfer completed.",
|
||||||
|
"ios-memory-limit": "Sending files to iOS is only possible up to 200MB at once",
|
||||||
|
"message-transfer-completed": "Message transfer completed.",
|
||||||
|
"unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?",
|
||||||
|
"rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.",
|
||||||
|
"selected-peer-left": "Selected peer left."
|
||||||
|
},
|
||||||
|
"document-titles": {
|
||||||
|
"file-received": "File Received",
|
||||||
|
"file-received-plural": "{{count}} Files Received",
|
||||||
|
"file-transfer-requested": "File Transfer Requested",
|
||||||
|
"message-received": "Message Received",
|
||||||
|
"message-received-plural": "{{count}} Messages Received"
|
||||||
|
},
|
||||||
|
"peer-ui": {
|
||||||
|
"click-to-send-paste-mode": "Click to send {{descriptor}}",
|
||||||
|
"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",
|
||||||
|
"preparing": "Preparing...",
|
||||||
|
"waiting": "Waiting...",
|
||||||
|
"processing": "Processing...",
|
||||||
|
"transferring": "Transferring..."
|
||||||
|
}
|
||||||
|
}
|
102
public/scripts/localization.js
Normal file
102
public/scripts/localization.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
class Localization {
|
||||||
|
constructor() {
|
||||||
|
Localization.defaultLocale = "en";
|
||||||
|
Localization.supportedLocales = ["en"];
|
||||||
|
|
||||||
|
Localization.translations = {};
|
||||||
|
|
||||||
|
const initialLocale = Localization.supportedOrDefault(Localization.browserLocales());
|
||||||
|
|
||||||
|
Localization.setLocale(initialLocale)
|
||||||
|
.then(_ => {
|
||||||
|
Localization.translatePage();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static isSupported(locale) {
|
||||||
|
return Localization.supportedLocales.indexOf(locale) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static supportedOrDefault(locales) {
|
||||||
|
return locales.find(Localization.isSupported) || Localization.defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
static browserLocales() {
|
||||||
|
return navigator.languages.map(locale =>
|
||||||
|
locale.split("-")[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setLocale(newLocale) {
|
||||||
|
if (newLocale === Localization.locale) return false;
|
||||||
|
|
||||||
|
const newTranslations = await Localization.fetchTranslationsFor(newLocale);
|
||||||
|
|
||||||
|
if(!newTranslations) return false;
|
||||||
|
|
||||||
|
const firstTranslation = !Localization.locale
|
||||||
|
|
||||||
|
Localization.locale = newLocale;
|
||||||
|
Localization.translations = newTranslations;
|
||||||
|
|
||||||
|
if (firstTranslation) {
|
||||||
|
Events.fire("translation-loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchTranslationsFor(newLocale) {
|
||||||
|
const response = await fetch(`lang/${newLocale}.json`)
|
||||||
|
|
||||||
|
if (response.redirected === true || response.status !== 200) return false;
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
static translatePage() {
|
||||||
|
document
|
||||||
|
.querySelectorAll("[data-i18n-key]")
|
||||||
|
.forEach(element => Localization.translateElement(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async translateElement(element) {
|
||||||
|
const key = element.getAttribute("data-i18n-key");
|
||||||
|
const attrs = element.getAttribute("data-i18n-attrs").split(" ");
|
||||||
|
|
||||||
|
for (let i in attrs) {
|
||||||
|
let attr = attrs[i];
|
||||||
|
if (attr === "text") {
|
||||||
|
element.innerText = await Localization.getTranslation(key);
|
||||||
|
} else {
|
||||||
|
element.attr = await Localization.getTranslation(key, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTranslation(key, attr, data) {
|
||||||
|
const keys = key.split(".");
|
||||||
|
|
||||||
|
let translationCandidates = Localization.translations;
|
||||||
|
|
||||||
|
for (let i=0; i<keys.length-1; i++) {
|
||||||
|
translationCandidates = translationCandidates[keys[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastKey = keys[keys.length-1];
|
||||||
|
if (attr) lastKey += "_" + attr;
|
||||||
|
|
||||||
|
let translation = translationCandidates[lastKey];
|
||||||
|
|
||||||
|
for (key in data) {
|
||||||
|
translation = translation.replace(`{{${key}}}`, data[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Localization.escapeHTML(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
static escapeHTML(unsafeText) {
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.innerText = unsafeText;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,12 +46,12 @@ class ServerConnection {
|
||||||
_onOpen() {
|
_onOpen() {
|
||||||
console.log('WS: server connected');
|
console.log('WS: server connected');
|
||||||
Events.fire('ws-connected');
|
Events.fire('ws-connected');
|
||||||
if (this._isReconnect) Events.fire('notify-user', 'Connected.');
|
if (this._isReconnect) Events.fire('notify-user', Localization.getTranslation("notifications.connected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPairDeviceInitiate() {
|
_onPairDeviceInitiate() {
|
||||||
if (!this._isConnected()) {
|
if (!this._isConnected()) {
|
||||||
Events.fire('notify-user', 'You need to be online to pair devices.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.online-requirement"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.send({ type: 'pair-device-initiate' })
|
this.send({ type: 'pair-device-initiate' })
|
||||||
|
@ -107,7 +107,7 @@ class ServerConnection {
|
||||||
Events.fire('pair-device-canceled', msg.roomKey);
|
Events.fire('pair-device-canceled', msg.roomKey);
|
||||||
break;
|
break;
|
||||||
case 'pair-device-join-key-rate-limit':
|
case 'pair-device-join-key-rate-limit':
|
||||||
Events.fire('notify-user', 'Rate limit reached. Wait 10 seconds and try again.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.rate-limit-join-key"));
|
||||||
break;
|
break;
|
||||||
case 'secret-room-deleted':
|
case 'secret-room-deleted':
|
||||||
Events.fire('secret-room-deleted', msg.roomSecret);
|
Events.fire('secret-room-deleted', msg.roomSecret);
|
||||||
|
@ -183,7 +183,7 @@ class ServerConnection {
|
||||||
|
|
||||||
_onDisconnect() {
|
_onDisconnect() {
|
||||||
console.log('WS: server disconnected');
|
console.log('WS: server disconnected');
|
||||||
Events.fire('notify-user', 'Connecting..');
|
Events.fire('notify-user', Localization.getTranslation("notifications.connecting"));
|
||||||
clearTimeout(this._reconnectTimer);
|
clearTimeout(this._reconnectTimer);
|
||||||
this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
|
this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
|
||||||
Events.fire('ws-disconnected');
|
Events.fire('ws-disconnected');
|
||||||
|
@ -488,7 +488,7 @@ class Peer {
|
||||||
|
|
||||||
_abortTransfer() {
|
_abortTransfer() {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
||||||
Events.fire('notify-user', 'Files are incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
|
||||||
this._filesReceived = [];
|
this._filesReceived = [];
|
||||||
this._requestAccepted = null;
|
this._requestAccepted = null;
|
||||||
this._digester = null;
|
this._digester = null;
|
||||||
|
@ -546,7 +546,7 @@ class Peer {
|
||||||
this._chunker = null;
|
this._chunker = null;
|
||||||
if (!this._filesQueue.length) {
|
if (!this._filesQueue.length) {
|
||||||
this._busy = false;
|
this._busy = false;
|
||||||
Events.fire('notify-user', '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
|
||||||
} else {
|
} else {
|
||||||
this._dequeueFile();
|
this._dequeueFile();
|
||||||
|
@ -558,7 +558,7 @@ class Peer {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
||||||
this._filesRequested = null;
|
this._filesRequested = null;
|
||||||
if (message.reason === 'ios-memory-limit') {
|
if (message.reason === 'ios-memory-limit') {
|
||||||
Events.fire('notify-user', "Sending files to iOS is only possible up to 200MB at once");
|
Events.fire('notify-user', Localization.getTranslation("notifications.ios-memory-limit"));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -568,7 +568,7 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageTransferCompleted() {
|
_onMessageTransferCompleted() {
|
||||||
Events.fire('notify-user', 'Message transfer completed.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendText(text) {
|
sendText(text) {
|
||||||
|
@ -713,7 +713,7 @@ class RTCPeer extends Peer {
|
||||||
_onBeforeUnload(e) {
|
_onBeforeUnload(e) {
|
||||||
if (this._busy) {
|
if (this._busy) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return "There are unfinished transfers. Are you sure you want to close?";
|
return Localization.getTranslation("notifications.unfinished-transfers-warning");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,12 @@ class PeersUI {
|
||||||
if (newDisplayName) {
|
if (newDisplayName) {
|
||||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
Events.fire('notify-user', 'Device name is changed permanently.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||||
Events.fire('notify-user', 'Device name is changed only for this session.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
Events.fire('self-display-name-changed', newDisplayName);
|
Events.fire('self-display-name-changed', newDisplayName);
|
||||||
|
@ -105,10 +105,9 @@ class PeersUI {
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||||
localStorage.removeItem('editedDisplayName');
|
localStorage.removeItem('editedDisplayName');
|
||||||
Events.fire('notify-user', 'Random Display name is used again.');
|
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
Events.fire('notify-user', 'Device name is randomly generated again.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
|
||||||
Events.fire('self-display-name-changed', '');
|
Events.fire('self-display-name-changed', '');
|
||||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
||||||
});
|
});
|
||||||
|
@ -275,21 +274,22 @@ class PeersUI {
|
||||||
let descriptor;
|
let descriptor;
|
||||||
let noPeersMessage;
|
let noPeersMessage;
|
||||||
|
|
||||||
|
const openPairDrop = Localization.getTranslation("dialogs.activate-paste-mode-base");
|
||||||
|
const andOtherFiles = Localization.getTranslation("dialogs.activate-paste-mode-and-other-files", null, {count: files.length-1});
|
||||||
|
const sharedText = Localization.getTranslation("dialogs.activate-paste-mode-shared-text");
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
descriptor = files[0].name;
|
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i>`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br><i>${descriptor}</i>`;
|
|
||||||
} else if (files.length > 1) {
|
} else if (files.length > 1) {
|
||||||
descriptor = `${files[0].name} and ${files.length-1} other files`;
|
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i> ${andOtherFiles}`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
|
||||||
} else {
|
} else {
|
||||||
descriptor = "shared text";
|
noPeersMessage = `${openPairDrop}<br>${sharedText}`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$xInstructions.querySelector('p').innerHTML = `<i>${descriptor}</i>`;
|
this.$xInstructions.querySelector('p').innerHTML = noPeersMessage;
|
||||||
this.$xInstructions.querySelector('p').style.display = 'block';
|
this.$xInstructions.querySelector('p').style.display = 'block';
|
||||||
this.$xInstructions.setAttribute('desktop', `Click to send`);
|
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.click-to-send"));
|
||||||
this.$xInstructions.setAttribute('mobile', `Tap to send`);
|
this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.tap-to-send"));
|
||||||
|
|
||||||
this.$xNoPeers.querySelector('h2').innerHTML = noPeersMessage;
|
this.$xNoPeers.querySelector('h2').innerHTML = noPeersMessage;
|
||||||
|
|
||||||
|
@ -320,10 +320,10 @@ class PeersUI {
|
||||||
this.$xInstructions.querySelector('p').innerText = '';
|
this.$xInstructions.querySelector('p').innerText = '';
|
||||||
this.$xInstructions.querySelector('p').style.display = 'none';
|
this.$xInstructions.querySelector('p').style.display = 'none';
|
||||||
|
|
||||||
this.$xInstructions.setAttribute('desktop', 'Click to send files or right click to send a message');
|
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.x-instructions", "desktop"));
|
||||||
this.$xInstructions.setAttribute('mobile', 'Tap to send files or long tap to send a message');
|
this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.x-instructions", "mobile"));
|
||||||
|
|
||||||
this.$xNoPeers.querySelector('h2').innerHTML = 'Open PairDrop on other devices to send files';
|
this.$xNoPeers.querySelector('h2').innerHTML = Localization.getTranslation("instructions.no-peers-title");
|
||||||
|
|
||||||
this.$cancelPasteModeBtn.setAttribute('hidden', "");
|
this.$cancelPasteModeBtn.setAttribute('hidden', "");
|
||||||
|
|
||||||
|
@ -368,9 +368,9 @@ class PeerUI {
|
||||||
let title;
|
let title;
|
||||||
let input = '';
|
let input = '';
|
||||||
if (window.pasteMode.activated) {
|
if (window.pasteMode.activated) {
|
||||||
title = `Click to send ${window.pasteMode.descriptor}`;
|
title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor});
|
||||||
} else {
|
} else {
|
||||||
title = 'Click to send files or right click to send a message';
|
title = Localization.getTranslation("peer-ui.click-to-send");
|
||||||
input = '<input type="file" multiple>';
|
input = '<input type="file" multiple>';
|
||||||
}
|
}
|
||||||
this.$el.innerHTML = `
|
this.$el.innerHTML = `
|
||||||
|
@ -392,7 +392,7 @@ class PeerUI {
|
||||||
<div class="name font-subheading"></div>
|
<div class="name font-subheading"></div>
|
||||||
<div class="device-name font-body2"></div>
|
<div class="device-name font-body2"></div>
|
||||||
<div class="status font-body2"></div>
|
<div class="status font-body2"></div>
|
||||||
<span class="connection-hash font-body2" title="To verify the security of the end-to-end encryption, compare this security number on both devices"></span>
|
<span class="connection-hash font-body2" title="${ Localization.getTranslation("peer-ui.connection-hash") }"></span>
|
||||||
</div>
|
</div>
|
||||||
</label>`;
|
</label>`;
|
||||||
|
|
||||||
|
@ -509,10 +509,23 @@ class PeerUI {
|
||||||
$progress.classList.remove('over50');
|
$progress.classList.remove('over50');
|
||||||
}
|
}
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
this.$el.setAttribute('status', status);
|
if (status !== this.currentStatus) {
|
||||||
|
let statusName = {
|
||||||
|
"prepare": Localization.getTranslation("peer-ui.preparing"),
|
||||||
|
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
||||||
|
"process": Localization.getTranslation("peer-ui.processing"),
|
||||||
|
"wait": Localization.getTranslation("peer-ui.waiting")
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
this.$el.setAttribute('status', status);
|
||||||
|
this.$el.querySelector('.status').innerText = statusName;
|
||||||
|
this.currentStatus = status;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$el.removeAttribute('status');
|
this.$el.removeAttribute('status');
|
||||||
|
this.$el.querySelector('.status').innerHTML = '';
|
||||||
progress = 0;
|
progress = 0;
|
||||||
|
this.currentStatus = null;
|
||||||
}
|
}
|
||||||
const degrees = `rotate(${360 * progress}deg)`;
|
const degrees = `rotate(${360 * progress}deg)`;
|
||||||
$progress.style.setProperty('--progress', degrees);
|
$progress.style.setProperty('--progress', degrees);
|
||||||
|
@ -595,7 +608,7 @@ class Dialog {
|
||||||
_onPeerDisconnected(peerId) {
|
_onPeerDisconnected(peerId) {
|
||||||
if (this.isShown() && this.correspondingPeerId === peerId) {
|
if (this.isShown() && this.correspondingPeerId === peerId) {
|
||||||
this.hide();
|
this.hide();
|
||||||
Events.fire('notify-user', 'Selected peer left.')
|
Events.fire('notify-user', Localization.getTranslation("notifications.selected-peer-left"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -629,13 +642,17 @@ class ReceiveDialog extends Dialog {
|
||||||
|
|
||||||
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||||
if (files.length > 1) {
|
if (files.length > 1) {
|
||||||
let fileOtherText = ` and ${files.length - 1} other `;
|
let fileOther;
|
||||||
if (files.length === 2) {
|
if (files.length === 2) {
|
||||||
fileOtherText += imagesOnly ? 'image' : 'file';
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||||
} else {
|
} else {
|
||||||
fileOtherText += imagesOnly ? 'images' : 'files';
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||||
}
|
}
|
||||||
this.$fileOther.innerText = fileOtherText;
|
this.$fileOther.innerText = fileOther;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = files[0].name;
|
const fileName = files[0].name;
|
||||||
|
@ -727,11 +744,15 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
|
|
||||||
let descriptor, url, filenameDownload;
|
let descriptor, url, filenameDownload;
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
descriptor = imagesOnly ? 'Image' : 'File';
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image")
|
||||||
|
: Localization.getTranslation("dialogs.title-file");
|
||||||
} else {
|
} else {
|
||||||
descriptor = imagesOnly ? 'Images' : 'Files';
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image-plural")
|
||||||
|
: Localization.getTranslation("dialogs.title-file-plural");
|
||||||
}
|
}
|
||||||
this.$receiveTitle.innerText = `${descriptor} Received`;
|
this.$receiveTitle.innerText = Localization.getTranslation("dialogs.receive-title", null, {descriptor: descriptor});
|
||||||
|
|
||||||
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
|
@ -781,7 +802,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$downloadBtn.innerText = "Download";
|
this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download");
|
||||||
this.$downloadBtn.onclick = _ => {
|
this.$downloadBtn.onclick = _ => {
|
||||||
if (downloadZipped) {
|
if (downloadZipped) {
|
||||||
let tmpZipBtn = document.createElement("a");
|
let tmpZipBtn = document.createElement("a");
|
||||||
|
@ -793,17 +814,18 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canShare) {
|
if (!canShare) {
|
||||||
this.$downloadBtn.innerText = "Download again";
|
this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download-again");
|
||||||
}
|
}
|
||||||
Events.fire('notify-user', `${descriptor} downloaded successfully`);
|
Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor}));
|
||||||
this.$downloadBtn.style.pointerEvents = "none";
|
this.$downloadBtn.style.pointerEvents = "none";
|
||||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.title = files.length === 1
|
document.title = files.length === 1
|
||||||
? 'File received - PairDrop'
|
? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
|
||||||
: `${files.length} Files received - PairDrop`;
|
: `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
|
|
||||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||||
this.show();
|
this.show();
|
||||||
|
|
||||||
|
@ -891,7 +913,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
||||||
|
|
||||||
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
||||||
|
|
||||||
document.title = `${request.imagesOnly ? 'Image' : 'File'} Transfer Requested - PairDrop`;
|
document.title = `${ Localization.getTranslation("document-titles.file-transfer-requested") } - PairDrop`;
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
this.show();
|
this.show();
|
||||||
}
|
}
|
||||||
|
@ -1083,7 +1105,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
this.hide();
|
this.hide();
|
||||||
Events.fire('notify-user', 'Pairing of two browser tabs is not possible.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-tabs-error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,7 +1151,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
|
|
||||||
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
Events.fire('notify-user', 'Devices paired successfully.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
|
||||||
this._evaluateNumberRoomSecrets();
|
this._evaluateNumberRoomSecrets();
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
|
@ -1137,13 +1159,13 @@ class PairDeviceDialog extends Dialog {
|
||||||
this.hide();
|
this.hide();
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Paired devices are not persistent.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-not-persistent"));
|
||||||
PersistentStorage.logBrowserNotCapable();
|
PersistentStorage.logBrowserNotCapable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceJoinKeyInvalid() {
|
_pairDeviceJoinKeyInvalid() {
|
||||||
Events.fire('notify-user', 'Key not valid');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceCancel() {
|
_pairDeviceCancel() {
|
||||||
|
@ -1153,7 +1175,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceCanceled(roomKey) {
|
_pairDeviceCanceled(roomKey) {
|
||||||
Events.fire('notify-user', `Key ${roomKey} invalidated.`);
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalidated", null, {key: roomKey}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanUp() {
|
_cleanUp() {
|
||||||
|
@ -1260,7 +1282,7 @@ class EditPairedDevicesDialog extends Dialog {
|
||||||
PersistentStorage.clearRoomSecrets().finally(_ => {
|
PersistentStorage.clearRoomSecrets().finally(_ => {
|
||||||
Events.fire('room-secrets-deleted', roomSecrets);
|
Events.fire('room-secrets-deleted', roomSecrets);
|
||||||
Events.fire('evaluate-number-room-secrets');
|
Events.fire('evaluate-number-room-secrets');
|
||||||
Events.fire('notify-user', 'All Devices unpaired.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
|
||||||
this.hide();
|
this.hide();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -1415,14 +1437,14 @@ class ReceiveTextDialog extends Dialog {
|
||||||
|
|
||||||
_setDocumentTitleMessages() {
|
_setDocumentTitleMessages() {
|
||||||
document.title = !this._receiveTextQueue.length
|
document.title = !this._receiveTextQueue.length
|
||||||
? 'Message Received - PairDrop'
|
? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
|
||||||
: `${this._receiveTextQueue.length + 1} Messages Received - PairDrop`;
|
: `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onCopy() {
|
async _onCopy() {
|
||||||
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
||||||
await navigator.clipboard.writeText(sanitizedText);
|
await navigator.clipboard.writeText(sanitizedText);
|
||||||
Events.fire('notify-user', 'Copied to clipboard');
|
Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard"));
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1449,13 +1471,13 @@ class Base64ZipDialog extends Dialog {
|
||||||
if (base64Text === "paste") {
|
if (base64Text === "paste") {
|
||||||
// ?base64text=paste
|
// ?base64text=paste
|
||||||
// base64 encoded string is ready to be pasted from clipboard
|
// base64 encoded string is ready to be pasted from clipboard
|
||||||
this.preparePasting("text");
|
this.preparePasting(Localization.getTranslation("dialogs.base64-text"));
|
||||||
} else if (base64Text === "hash") {
|
} else if (base64Text === "hash") {
|
||||||
// ?base64text=hash#BASE64ENCODED
|
// ?base64text=hash#BASE64ENCODED
|
||||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||||
this.processBase64Text(base64Hash)
|
this.processBase64Text(base64Hash)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Text content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||||
console.log("Text content incorrect.");
|
console.log("Text content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1465,7 +1487,7 @@ class Base64ZipDialog extends Dialog {
|
||||||
// base64 encoded string was part of url param (not recommended)
|
// base64 encoded string was part of url param (not recommended)
|
||||||
this.processBase64Text(base64Text)
|
this.processBase64Text(base64Text)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Text content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||||
console.log("Text content incorrect.");
|
console.log("Text content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1478,32 +1500,32 @@ class Base64ZipDialog extends Dialog {
|
||||||
// base64 encoded zip file is url hash which is never sent to the server
|
// base64 encoded zip file is url hash which is never sent to the server
|
||||||
this.processBase64Zip(base64Hash)
|
this.processBase64Zip(base64Hash)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'File content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
|
||||||
console.log("File content incorrect.");
|
console.log("File content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// ?base64zip=paste || ?base64zip=true
|
// ?base64zip=paste || ?base64zip=true
|
||||||
this.preparePasting('files');
|
this.preparePasting(Localization.getTranslation("dialogs.base64-files"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setPasteBtnToProcessing() {
|
_setPasteBtnToProcessing() {
|
||||||
this.$pasteBtn.style.pointerEvents = "none";
|
this.$pasteBtn.style.pointerEvents = "none";
|
||||||
this.$pasteBtn.innerText = "Processing...";
|
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing");
|
||||||
}
|
}
|
||||||
|
|
||||||
preparePasting(type) {
|
preparePasting(type) {
|
||||||
if (navigator.clipboard.readText) {
|
if (navigator.clipboard.readText) {
|
||||||
this.$pasteBtn.innerText = `Tap here to paste ${type}`;
|
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", {type: type});
|
||||||
this._clickCallback = _ => this.processClipboard(type);
|
this._clickCallback = _ => this.processClipboard(type);
|
||||||
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
||||||
} else {
|
} else {
|
||||||
console.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.")
|
console.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.")
|
||||||
this.$pasteBtn.setAttribute('hidden', '');
|
this.$pasteBtn.setAttribute('hidden', '');
|
||||||
this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`);
|
this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", {type: type}));
|
||||||
this.$fallbackTextarea.removeAttribute('hidden');
|
this.$fallbackTextarea.removeAttribute('hidden');
|
||||||
this._inputCallback = _ => this.processInput(type);
|
this._inputCallback = _ => this.processInput(type);
|
||||||
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
||||||
|
@ -1543,7 +1565,7 @@ class Base64ZipDialog extends Dialog {
|
||||||
await this.processBase64Zip(base64);
|
await this.processBase64Zip(base64);
|
||||||
}
|
}
|
||||||
} catch(_) {
|
} catch(_) {
|
||||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.clipboard-content-incorrect"));
|
||||||
console.log("Clipboard content is incorrect.")
|
console.log("Clipboard content is incorrect.")
|
||||||
}
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1626,7 +1648,7 @@ class Notifications {
|
||||||
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
|
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Events.fire('notify-user', 'Notifications enabled.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
|
||||||
this.$button.setAttribute('hidden', 1);
|
this.$button.setAttribute('hidden', 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1661,10 +1683,10 @@ class Notifications {
|
||||||
if (document.visibilityState !== 'visible') {
|
if (document.visibilityState !== 'visible') {
|
||||||
const peerDisplayName = $(peerId).ui._displayName();
|
const peerDisplayName = $(peerId).ui._displayName();
|
||||||
if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
|
if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
|
||||||
const notification = this._notify(`Link received by ${peerDisplayName} - Click to open`, message);
|
const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message);
|
||||||
this._bind(notification, _ => window.open(message, '_blank', null, true));
|
this._bind(notification, _ => window.open(message, '_blank', null, true));
|
||||||
} else {
|
} else {
|
||||||
const notification = this._notify(`Message received by ${peerDisplayName} - Click to copy`, message);
|
const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message);
|
||||||
this._bind(notification, _ => this._copyText(message, notification));
|
this._bind(notification, _ => this._copyText(message, notification));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1679,13 +1701,23 @@ class Notifications {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let title = files[0].name;
|
let title;
|
||||||
if (files.length >= 2) {
|
if (files.length === 1) {
|
||||||
title += ` and ${files.length - 1} other `;
|
title = `${files[0].name}`;
|
||||||
title += imagesOnly ? 'image' : 'file';
|
} else {
|
||||||
if (files.length > 2) title += "s";
|
let fileOther;
|
||||||
|
if (files.length === 2) {
|
||||||
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||||
|
} else {
|
||||||
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||||
|
}
|
||||||
|
title = `${files[0].name} ${fileOther}`
|
||||||
}
|
}
|
||||||
const notification = this._notify(title, 'Click to download');
|
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-download"));
|
||||||
this._bind(notification, _ => this._download(notification));
|
this._bind(notification, _ => this._download(notification));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1699,15 +1731,27 @@ class Notifications {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let descriptor;
|
|
||||||
if (request.header.length > 1) {
|
|
||||||
descriptor = imagesOnly ? ' images' : ' files';
|
|
||||||
} else {
|
|
||||||
descriptor = imagesOnly ? ' image' : ' file';
|
|
||||||
}
|
|
||||||
let displayName = $(peerId).querySelector('.name').textContent
|
let displayName = $(peerId).querySelector('.name').textContent
|
||||||
let title = `${displayName} would like to transfer ${request.header.length} ${descriptor}`;
|
|
||||||
const notification = this._notify(title, 'Click to show');
|
let descriptor;
|
||||||
|
if (request.header.length === 1) {
|
||||||
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image")
|
||||||
|
: Localization.getTranslation("dialogs.title-file");
|
||||||
|
} else {
|
||||||
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image-plural")
|
||||||
|
: Localization.getTranslation("dialogs.title-file-plural");
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = Localization.getTranslation("notifications.request-title", null, {
|
||||||
|
name: displayName,
|
||||||
|
count: request.header.length,
|
||||||
|
descriptor: descriptor.toLowerCase()
|
||||||
|
});
|
||||||
|
|
||||||
|
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1719,10 +1763,9 @@ class Notifications {
|
||||||
_copyText(message, notification) {
|
_copyText(message, notification) {
|
||||||
if (navigator.clipboard.writeText(message)) {
|
if (navigator.clipboard.writeText(message)) {
|
||||||
notification.close();
|
notification.close();
|
||||||
this._notify('Copied text to clipboard');
|
this._notify(Localization.getTranslation("notifications.copied-text"));
|
||||||
} else {
|
} else {
|
||||||
this._notify('Writing to clipboard failed. Copy manually!');
|
this._notify(Localization.getTranslation("notifications.copied-text-error"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1746,11 +1789,11 @@ class NetworkStatusUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showOfflineMessage() {
|
_showOfflineMessage() {
|
||||||
Events.fire('notify-user', 'You are offline');
|
Events.fire('notify-user', Localization.getTranslation("notifications.offline"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_showOnlineMessage() {
|
_showOnlineMessage() {
|
||||||
Events.fire('notify-user', 'You are back online');
|
Events.fire('notify-user', Localization.getTranslation("notifications.online"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2208,7 +2251,7 @@ class BrowserTabsConnector {
|
||||||
|
|
||||||
class PairDrop {
|
class PairDrop {
|
||||||
constructor() {
|
constructor() {
|
||||||
Events.on('load', _ => {
|
Events.on('translation-loaded', _ => {
|
||||||
const server = new ServerConnection();
|
const server = new ServerConnection();
|
||||||
const peers = new PeersManager(server);
|
const peers = new PeersManager(server);
|
||||||
const peersUI = new PeersUI();
|
const peersUI = new PeersUI();
|
||||||
|
@ -2232,6 +2275,7 @@ class PairDrop {
|
||||||
|
|
||||||
const persistentStorage = new PersistentStorage();
|
const persistentStorage = new PersistentStorage();
|
||||||
const pairDrop = new PairDrop();
|
const pairDrop = new PairDrop();
|
||||||
|
const localization = new Localization();
|
||||||
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
|
|
|
@ -442,7 +442,7 @@ x-no-peers::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
x-no-peers[drop-bg]::before {
|
x-no-peers[drop-bg]::before {
|
||||||
content: "Release to select recipient";
|
content: attr(data-drop-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
x-no-peers[drop-bg] * {
|
x-no-peers[drop-bg] * {
|
||||||
|
@ -553,22 +553,6 @@ x-peer[status] x-icon {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
x-peer[status=transfer] .status:before {
|
|
||||||
content: 'Transferring...';
|
|
||||||
}
|
|
||||||
|
|
||||||
x-peer[status=prepare] .status:before {
|
|
||||||
content: 'Preparing...';
|
|
||||||
}
|
|
||||||
|
|
||||||
x-peer[status=wait] .status:before {
|
|
||||||
content: 'Waiting...';
|
|
||||||
}
|
|
||||||
|
|
||||||
x-peer[status=process] .status:before {
|
|
||||||
content: 'Processing...';
|
|
||||||
}
|
|
||||||
|
|
||||||
x-peer:not([status]) .status,
|
x-peer:not([status]) .status,
|
||||||
x-peer[status] .device-name {
|
x-peer[status] .device-name {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -626,11 +610,13 @@ footer .font-body2 {
|
||||||
#on-this-network {
|
#on-this-network {
|
||||||
border-bottom: solid 4px var(--primary-color);
|
border-bottom: solid 4px var(--primary-color);
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
|
word-break: keep-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
#paired-devices {
|
#paired-devices {
|
||||||
border-bottom: solid 4px var(--paired-device-color);
|
border-bottom: solid 4px var(--paired-device-color);
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
|
word-break: keep-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
#display-name {
|
#display-name {
|
||||||
|
@ -723,10 +709,6 @@ x-dialog a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
x-dialog .font-subheading {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pair Devices Dialog */
|
/* Pair Devices Dialog */
|
||||||
|
|
||||||
#key-input-container {
|
#key-input-container {
|
||||||
|
@ -774,6 +756,10 @@ x-dialog .font-subheading {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pair-instructions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
x-dialog hr {
|
x-dialog hr {
|
||||||
margin: 40px -24px 30px -24px;
|
margin: 40px -24px 30px -24px;
|
||||||
border: solid 1.25px var(--border-color);
|
border: solid 1.25px var(--border-color);
|
||||||
|
@ -785,7 +771,7 @@ x-dialog hr {
|
||||||
|
|
||||||
/* Edit Paired Devices Dialog */
|
/* Edit Paired Devices Dialog */
|
||||||
.paired-devices-wrapper:empty:before {
|
.paired-devices-wrapper:empty:before {
|
||||||
content: "No paired devices.";
|
content: attr(data-empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
.paired-devices-wrapper:empty {
|
.paired-devices-wrapper:empty {
|
||||||
|
@ -1288,11 +1274,11 @@ x-instructions:not([drop-peer]):not([drop-bg]):before {
|
||||||
}
|
}
|
||||||
|
|
||||||
x-instructions[drop-peer]:before {
|
x-instructions[drop-peer]:before {
|
||||||
content: "Release to send to peer";
|
content: attr(data-drop-peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
x-instructions[drop-bg]:not([drop-peer]):before {
|
x-instructions[drop-bg]:not([drop-peer]):before {
|
||||||
content: "Release to select recipient";
|
content: attr(data-drop-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
x-instructions p {
|
x-instructions p {
|
||||||
|
|
|
@ -39,62 +39,66 @@
|
||||||
|
|
||||||
<body translate="no">
|
<body translate="no">
|
||||||
<header class="row-reverse">
|
<header class="row-reverse">
|
||||||
<a href="#about" class="icon-button" title="About PairDrop" aria-label="Open About PairDrop">
|
<a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label" title="About PairDrop" aria-label="Open About PairDrop">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#info-outline" />
|
<use xlink:href="#info-outline" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div id="theme-wrapper">
|
<div id="theme-wrapper">
|
||||||
<div id="theme-auto" class="icon-button selected" title="Adapt Theme to System" >
|
<div id="theme-auto" class="icon-button selected" data-i18n-key="header.theme-auto" data-i18n-attrs="title" title="Adapt Theme to System" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-auto" />
|
<use xlink:href="#icon-theme-auto" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div id="theme-light" class="icon-button" title="Always Use Light-Theme" >
|
<div id="theme-light" class="icon-button" data-i18n-key="header.theme-light" data-i18n-attrs="title" title="Always Use Light-Theme" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-light" />
|
<use xlink:href="#icon-theme-light" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="theme-dark" class="icon-button" title="Always Use Dark-Theme" >
|
<div id="theme-dark" class="icon-button" data-i18n-key="header.theme-dark" data-i18n-attrs="title" title="Always Use Dark-Theme" >
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#icon-theme-dark" />
|
<use xlink:href="#icon-theme-dark" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification" class="icon-button" title="Enable Notifications" hidden>
|
<div id="notification" class="icon-button" data-i18n-key="header.notification" data-i18n-attrs="title" title="Enable Notifications" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#notifications" />
|
<use xlink:href="#notifications" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="install" class="icon-button" title="Install PairDrop" hidden>
|
<div id="install" class="icon-button" data-i18n-key="header.install" data-i18n-attrs="title" title="Install PairDrop" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#homescreen" />
|
<use xlink:href="#homescreen" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="pair-device" class="icon-button" title="Pair Device" hidden>
|
<div id="pair-device" class="icon-button" data-i18n-key="header.pair-device" data-i18n-attrs="title" title="Pair Device" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#pair-device-icon" />
|
<use xlink:href="#pair-device-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="edit-paired-devices" class="icon-button" title="Edit Paired Devices" hidden>
|
<div id="edit-paired-devices" class="icon-button" data-i18n-key="header.edit-paired-devices" data-i18n-attrs="title" title="Edit Paired Devices" hidden>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#edit-pair-devices-icon" />
|
<use xlink:href="#edit-pair-devices-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="cancel-paste-mode" class="button" hidden>Done</div>
|
<div id="cancel-paste-mode" class="button" data-i18n-key="header.done" data-i18n-attrs="text" hidden>Done</div>
|
||||||
</header>
|
</header>
|
||||||
<!-- Center -->
|
<!-- Center -->
|
||||||
<div id="center">
|
<div id="center">
|
||||||
<!-- Peers -->
|
<!-- Peers -->
|
||||||
<div class="x-peers-filler"></div>
|
<div class="x-peers-filler"></div>
|
||||||
<x-peers class="center"></x-peers>
|
<x-peers class="center"></x-peers>
|
||||||
<x-no-peers>
|
<x-no-peers data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg" data-drop-bg="Release to select recipient">
|
||||||
<h2>Open PairDrop on other devices to send files</h2>
|
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text">Open PairDrop on other devices to send files</h2>
|
||||||
<div>Pair devices to be discoverable on other networks</div>
|
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text">Pair devices to be discoverable on other networks</div>
|
||||||
</x-no-peers>
|
</x-no-peers>
|
||||||
<x-instructions desktop="Click to send files or right click to send a message" mobile="Tap to send files or long tap to send a message">
|
<x-instructions data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg"
|
||||||
|
desktop="Click to send files or right click to send a message"
|
||||||
|
mobile="Tap to send files or long tap to send a message"
|
||||||
|
data-drop-peer="Release to send to peer"
|
||||||
|
data-drop-bg="Release to select recipient">
|
||||||
<p id="paste-filename"></p>
|
<p id="paste-filename"></p>
|
||||||
</x-instructions>
|
</x-instructions>
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,18 +108,26 @@
|
||||||
<use xlink:href="#wifi-tethering" />
|
<use xlink:href="#wifi-tethering" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<span>You are known as:</span>
|
<span data-i18n-key="footer.known-as" data-i18n-attrs="text">You are known as:</span>
|
||||||
<div id="display-name" placeholder="Loading..." title="Edit your device name permanently" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div>
|
<div id="display-name" data-i18n-key="footer.display-name" data-i18n-attrs="placeholder title" placeholder="Loading..." title="Edit your device name permanently" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div>
|
||||||
<svg id="edit-pen" class="icon">
|
<svg id="edit-pen" class="icon">
|
||||||
<use xlink:href="#edit-pen-icon" />
|
<use xlink:href="#edit-pen-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-body2">
|
<div class="font-body2">
|
||||||
You can be discovered by everyone <span id="on-this-network">on this network</span>
|
<div>
|
||||||
<span id="and-by-paired-devices" hidden> and by <span id="paired-devices">paired devices</span></span>
|
<span data-i18n-key="footer.discovery-everyone" data-i18n-attrs="text">You can be discovered by everyone</span>
|
||||||
|
<span id="on-this-network" data-i18n-key="footer.on-this-network" data-i18n-attrs="text">on this network</span>
|
||||||
|
</div>
|
||||||
|
<div id="and-by-paired-devices" hidden>
|
||||||
|
<span id="and-by" data-i18n-key="footer.and-by" data-i18n-attrs="text">and by</span>
|
||||||
|
<span id="paired-devices" data-i18n-key="footer.paired-devices" data-i18n-attrs="text">paired devices</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="websocket-fallback">
|
<div id="websocket-fallback">
|
||||||
<span>Traffic is <span>routed through the server</span> if WebRTC is not available.</span>
|
<span data-i18n-key="footer.traffic" data-i18n-attrs="text">Traffic is</span>
|
||||||
|
<span data-i18n-key="footer.routed" data-i18n-attrs="text">routed through the server</span>
|
||||||
|
<span data-i18n-key="footer.webrtc" data-i18n-attrs="text">if WebRTC is not available.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<!-- Pair Device Dialog -->
|
<!-- Pair Device Dialog -->
|
||||||
|
@ -123,10 +135,13 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center text-center">
|
<x-background class="full center text-center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="center">Pair Devices</h2>
|
<h2 class="center" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text">Pair Devices</h2>
|
||||||
<div id="room-key-qr-code" class="center"></div>
|
<div id="room-key-qr-code" class="center"></div>
|
||||||
<h1 id="room-key" class="center">000 000</h1>
|
<h1 id="room-key" class="center">000 000</h1>
|
||||||
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
|
<div id="pair-instructions" class="center text-center">
|
||||||
|
<span class="font-subheading" data-i18n-key="dialogs.input-key-on-this-device" data-i18n-attrs="text">Input this key on another device</span>
|
||||||
|
<span class="font-subheading" data-i18n-key="dialogs.scan-qr-code" data-i18n-attrs="text">or scan the QR-Code.</span>
|
||||||
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div id="key-input-container">
|
<div id="key-input-container">
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||||
|
@ -136,10 +151,10 @@
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||||
<input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
<input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-subheading center text-center">Enter key from another device to continue.</div>
|
<div class="font-subheading center text-center" data-i18n-key="dialogs.enter-key-from-another-device" data-i18n-attrs="text">Enter key from another device to continue.</div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" disabled>Pair</button>
|
<button class="button" type="submit" data-i18n-key="dialogs.pair" data-i18n-attrs="text" disabled>Pair</button>
|
||||||
<button class="button" type="button" close>Cancel</button>
|
<button class="button" type="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -150,13 +165,21 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center text-center">
|
<x-background class="full center text-center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="center">Edit Paired Devices</h2>
|
<h2 class="center" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text">Edit Paired Devices</h2>
|
||||||
<div class="paired-devices-wrapper"></div>
|
<div class="paired-devices-wrapper" data-i18n-key="dialogs.paired-devices-empty" data-i18n-attrs="data-empty" data-empty="No paired devices."></div>
|
||||||
<div class="font-subheading center">
|
<div class="font-subheading center">
|
||||||
<p>Activate <u>auto-accept</u> to automatically accept all files sent from that device.</p>
|
<p>
|
||||||
|
<span data-i18n-key="dialogs.auto-accept-instructions-1" data-i18n-attrs="text">
|
||||||
|
Activate
|
||||||
|
</span>
|
||||||
|
<u data-i18n-key="dialogs.auto-accept" data-i18n-attrs="text">auto-accept</u>
|
||||||
|
<span data-i18n-key="dialogs.auto-accept-instructions-2" data-i18n-attrs="text">
|
||||||
|
to automatically accept all files sent from that device.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="button" close>Close</button>
|
<button class="button" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -170,7 +193,7 @@
|
||||||
<div class="center column file-description">
|
<div class="center column file-description">
|
||||||
<div>
|
<div>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>would like to share</span>
|
<span data-i18n-key="dialogs.would-like-to-share" data-i18n-attrs="text">would like to share</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row file-name" >
|
<div class="row file-name" >
|
||||||
<span class="file-stem"></span>
|
<span class="file-stem"></span>
|
||||||
|
@ -182,8 +205,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="center file-preview"></div>
|
<div class="center file-preview"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="accept-request" class="button" title="ENTER" autofocus>Accept</button>
|
<button id="accept-request" class="button" title="ENTER" data-i18n-key="dialogs.accept" data-i18n-attrs="text" autofocus>Accept</button>
|
||||||
<button id="decline-request" class="button" title="ESCAPE">Decline</button>
|
<button id="decline-request" class="button" title="ESCAPE" data-i18n-key="dialogs.decline" data-i18n-attrs="text">Decline</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -196,7 +219,7 @@
|
||||||
<div class="center column file-description">
|
<div class="center column file-description">
|
||||||
<div>
|
<div>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>has sent</span>
|
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text">has sent</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row file-name" >
|
<div class="row file-name" >
|
||||||
<span class="file-stem"></span>
|
<span class="file-stem"></span>
|
||||||
|
@ -207,9 +230,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="center file-preview"></div>
|
<div class="center file-preview"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="share-btn" class="button" autofocus hidden>Share</button>
|
<button id="share-btn" class="button" data-i18n-key="dialogs.share" data-i18n-attrs="text" autofocus hidden>Share</button>
|
||||||
<button id="download-btn" class="button" autofocus>Download</button>
|
<button id="download-btn" class="button" data-i18n-key="dialogs.download" data-i18n-attrs="text" autofocus>Download</button>
|
||||||
<button class="button" close>Close</button>
|
<button class="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -219,16 +242,16 @@
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="text-center">Send Message</h2>
|
<h2 class="text-center" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text">Send Message</h2>
|
||||||
<div class="dialog-subheader text-center">
|
<div class="dialog-subheader text-center">
|
||||||
<span>Send a Message to</span>
|
<span data-i18n-key="dialogs.send-message-to" data-i18n-attrs="text">Send a Message to</span>
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
|
<button class="button" type="submit" title="CTRL/⌘ + ENTER" data-i18n-key="dialogs.send" data-i18n-attrs="text" disabled>Send</button>
|
||||||
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
<button class="button" type="button" title="ESCAPE" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -238,16 +261,16 @@
|
||||||
<x-dialog id="receive-text-dialog">
|
<x-dialog id="receive-text-dialog">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<h2 class="text-center">Message Received</h2>
|
<h2 class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text">Message Received</h2>
|
||||||
<div class="text-center dialog-subheader">
|
<div class="text-center dialog-subheader">
|
||||||
<span class="display-name"></span>
|
<span class="display-name"></span>
|
||||||
<span>has sent:</span>
|
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text">has sent:</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text"></div>
|
<div id="text"></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button id="copy" class="button" title="CTRL/⌘ + C">Copy</button>
|
<button id="copy" class="button" title="CTRL/⌘ + C" data-i18n-key="dialogs.copy" data-i18n-attrs="text">Copy</button>
|
||||||
<button id="close" class="button" title="ESCAPE">Close</button>
|
<button id="close" class="button" title="ESCAPE" data-i18n-key="dialogs.close" data-i18n-attrs="text">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
|
@ -256,9 +279,9 @@
|
||||||
<x-dialog id="base64-paste-dialog">
|
<x-dialog id="base64-paste-dialog">
|
||||||
<x-background class="full center">
|
<x-background class="full center">
|
||||||
<x-paper shadow="2">
|
<x-paper shadow="2">
|
||||||
<button class="button center" id="base64-paste-btn" title="Paste">Tap here to paste files</button>
|
<button class="button center" id="base64-paste-btn" title="Paste"></button>
|
||||||
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
||||||
<button class="button center" close>Close</button>
|
<button class="button center" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
</x-background>
|
</x-background>
|
||||||
</x-dialog>
|
</x-dialog>
|
||||||
|
@ -269,7 +292,7 @@
|
||||||
<!-- About Page -->
|
<!-- About Page -->
|
||||||
<x-about id="about" class="full center column">
|
<x-about id="about" class="full center column">
|
||||||
<header class="row-reverse fade-in">
|
<header class="row-reverse fade-in">
|
||||||
<a href="#" class="close icon-button" aria-label="Close About PairDrop">
|
<a href="#" class="close icon-button" data-i18n-key="about.close-about" data-i18n-attrs="text" aria-label="Close About PairDrop">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlink:href="#close-icon" />
|
<use xlink:href="#close-icon" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -283,7 +306,7 @@
|
||||||
<h1>PairDrop</h1>
|
<h1>PairDrop</h1>
|
||||||
<div class="font-subheading">v1.7.6</div>
|
<div class="font-subheading">v1.7.6</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
<div class="font-subheading" data-i18n-key="about.claim" data-i18n-attrs="text">The easiest way to transfer files across devices</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
|
@ -376,6 +399,7 @@
|
||||||
</symbol>
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
|
<script src="scripts/localization.js"></script>
|
||||||
<script src="scripts/theme.js"></script>
|
<script src="scripts/theme.js"></script>
|
||||||
<script src="scripts/network.js"></script>
|
<script src="scripts/network.js"></script>
|
||||||
<script src="scripts/ui.js"></script>
|
<script src="scripts/ui.js"></script>
|
||||||
|
|
136
public_included_ws_fallback/lang/en.json
Normal file
136
public_included_ws_fallback/lang/en.json
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"about_title": "About PairDrop",
|
||||||
|
"about_aria-label": "Open About PairDrop",
|
||||||
|
"theme-auto_title": "Adapt Theme to System",
|
||||||
|
"theme-light_title": "Always Use Light-Theme",
|
||||||
|
"theme-dark_title": "Always Use Dark-Theme",
|
||||||
|
"notification_title": "Enable Notifications",
|
||||||
|
"install_title": "Install PairDrop",
|
||||||
|
"pair-device_title": "Pair Device",
|
||||||
|
"edit-paired-devices_title": "Edit Paired Devices",
|
||||||
|
"cancel-paste-mode": "Done"
|
||||||
|
},
|
||||||
|
"instructions": {
|
||||||
|
"no-peers_data-drop-bg": "Release to select recipient",
|
||||||
|
"no-peers-title": "Open PairDrop on other devices to send files",
|
||||||
|
"no-peers-subtitle": "Pair devices to be discoverable on other networks",
|
||||||
|
"x-instructions_desktop": "Click to send files or right click to send a message",
|
||||||
|
"x-instructions_mobile": "Tap to send files or long tap to send a message",
|
||||||
|
"x-instructions_data-drop-peer": "Release to send to peer",
|
||||||
|
"x-instructions_data-drop-bg": "Release to select recipient",
|
||||||
|
"click-to-send": "Click to send",
|
||||||
|
"tap-to-send": "Tap to send"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"known-as": "You are known as:",
|
||||||
|
"display-name_placeholder": "Loading...",
|
||||||
|
"display-name_title": "Edit your device name permanently",
|
||||||
|
"discovery-everyone": "You can be discovered by everyone",
|
||||||
|
"on-this-network": "on this network",
|
||||||
|
"and-by": "and by",
|
||||||
|
"paired-devices": "paired devices",
|
||||||
|
"traffic": "Traffic is",
|
||||||
|
"routed": "routed through the server",
|
||||||
|
"webrtc": "if WebRTC is not available."
|
||||||
|
},
|
||||||
|
"dialogs": {
|
||||||
|
"activate-paste-mode-base": "Open PairDrop on other devices to send",
|
||||||
|
"activate-paste-mode-and-other-files": "and {{count}} other files",
|
||||||
|
"activate-paste-mode-activate-paste-mode-shared-text": "shared text",
|
||||||
|
"pair-devices-title": "Pair Devices",
|
||||||
|
"input-key-on-this-device": "Input this key on another device",
|
||||||
|
"scan-qr-code": "or scan the QR-Code.",
|
||||||
|
"enter-key-from-another-device": "Enter key from another device to continue.",
|
||||||
|
"pair": "Pair",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"edit-paired-devices-title": "Edit Paired Devices",
|
||||||
|
"paired-devices-wrapper_data-empty": "No paired devices.",
|
||||||
|
"auto-accept-instructions-1": "Activate",
|
||||||
|
"auto-accept": "auto-accept",
|
||||||
|
"auto-accept-instructions-2": "to automatically accept all files sent from that device.",
|
||||||
|
"close": "Close",
|
||||||
|
"would-like-to-share": "would like to share",
|
||||||
|
"accept": "Accept",
|
||||||
|
"decline": "Decline",
|
||||||
|
"has-sent": "has sent:",
|
||||||
|
"share": "Share",
|
||||||
|
"download": "Download",
|
||||||
|
"send-message-title": "Send Message",
|
||||||
|
"send-message-to": "Send a Message to",
|
||||||
|
"send": "Send",
|
||||||
|
"receive-text-title": "Message Received",
|
||||||
|
"copy": "Copy",
|
||||||
|
"base64-processing": "Processing...",
|
||||||
|
"base64-tap-to-paste": "Tap here to paste {{type}}",
|
||||||
|
"base64-paste-to-send": "Paste here to send {{type}}",
|
||||||
|
"base64-text": "text",
|
||||||
|
"base64-files": "files",
|
||||||
|
"file-other-description-image": "and 1 other image",
|
||||||
|
"file-other-description-file": "and 1 other file",
|
||||||
|
"file-other-description-image-plural": "and {{count}} other images",
|
||||||
|
"file-other-description-file-plural": "and {{count}} other files",
|
||||||
|
"title-image": "Image",
|
||||||
|
"title-file": "File",
|
||||||
|
"title-image-plural": "Images",
|
||||||
|
"title-file-plural": "Files",
|
||||||
|
"receive-title": "{{descriptor}} Received",
|
||||||
|
"download-again": "Download again"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"close-about-aria-label": "Close About PairDrop",
|
||||||
|
"claim": "The easiest way to transfer files across devices"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"display-name-changed-permanently": "Display name is changed permanently.",
|
||||||
|
"display-name-changed-temporarily": "Display name is changed only for this session.",
|
||||||
|
"display-name-random-again": "Display name is randomly generated again.",
|
||||||
|
"download-successful": "{{descriptor}} downloaded successfully",
|
||||||
|
"pairing-tabs-error": "Pairing of two browser tabs is not possible.",
|
||||||
|
"pairing-success": "Devices paired successfully.",
|
||||||
|
"pairing-not-persistent": "Paired devices are not persistent.",
|
||||||
|
"pairing-key-invalid": "Key not valid",
|
||||||
|
"pairing-key-invalidated": "Key {{key}} invalidated.",
|
||||||
|
"pairing-cleared": "All Devices unpaired.",
|
||||||
|
"copied-to-clipboard": "Copied to clipboard",
|
||||||
|
"text-content-incorrect": "Text content is incorrect.",
|
||||||
|
"file-content-incorrect": "File content is incorrect.",
|
||||||
|
"clipboard-content-incorrect": "Clipboard content is incorrect.",
|
||||||
|
"notifications-enabled": "Notifications enabled.",
|
||||||
|
"link-received": "Link received by {{name}} - Click to open",
|
||||||
|
"message-received": "Message received by {{name}} - Click to copy",
|
||||||
|
"click-to-download": "Click to download",
|
||||||
|
"request-title": "{{name}} would like to transfer {{count}} {{descriptor}}",
|
||||||
|
"click-to-show": "Click to show",
|
||||||
|
"copied-text": "Copied text to clipboard",
|
||||||
|
"copied-text-error": "Writing to clipboard failed. Copy manually!",
|
||||||
|
"offline": "You are offline",
|
||||||
|
"online": "You are back online",
|
||||||
|
"connected": "Connected.",
|
||||||
|
"online-requirement": "You need to be online to pair devices.",
|
||||||
|
"connecting": "Connecting...",
|
||||||
|
"files-incorrect": "Files are incorrect.",
|
||||||
|
"file-transfer-completed": "File transfer completed.",
|
||||||
|
"ios-memory-limit": "Sending files to iOS is only possible up to 200MB at once",
|
||||||
|
"message-transfer-completed": "Message transfer completed.",
|
||||||
|
"unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?",
|
||||||
|
"rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.",
|
||||||
|
"selected-peer-left": "Selected peer left."
|
||||||
|
},
|
||||||
|
"document-titles": {
|
||||||
|
"file-received": "File Received",
|
||||||
|
"file-received-plural": "{{count}} Files Received",
|
||||||
|
"file-transfer-requested": "File Transfer Requested",
|
||||||
|
"message-received": "Message Received",
|
||||||
|
"message-received-plural": "{{count}} Messages Received"
|
||||||
|
},
|
||||||
|
"peer-ui": {
|
||||||
|
"click-to-send-paste-mode": "Click to send {{descriptor}}",
|
||||||
|
"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",
|
||||||
|
"preparing": "Preparing...",
|
||||||
|
"waiting": "Waiting...",
|
||||||
|
"processing": "Processing...",
|
||||||
|
"transferring": "Transferring..."
|
||||||
|
}
|
||||||
|
}
|
102
public_included_ws_fallback/scripts/localization.js
Normal file
102
public_included_ws_fallback/scripts/localization.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
class Localization {
|
||||||
|
constructor() {
|
||||||
|
Localization.defaultLocale = "en";
|
||||||
|
Localization.supportedLocales = ["en"];
|
||||||
|
|
||||||
|
Localization.translations = {};
|
||||||
|
|
||||||
|
const initialLocale = Localization.supportedOrDefault(Localization.browserLocales());
|
||||||
|
|
||||||
|
Localization.setLocale(initialLocale)
|
||||||
|
.then(_ => {
|
||||||
|
Localization.translatePage();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static isSupported(locale) {
|
||||||
|
return Localization.supportedLocales.indexOf(locale) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static supportedOrDefault(locales) {
|
||||||
|
return locales.find(Localization.isSupported) || Localization.defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
static browserLocales() {
|
||||||
|
return navigator.languages.map(locale =>
|
||||||
|
locale.split("-")[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setLocale(newLocale) {
|
||||||
|
if (newLocale === Localization.locale) return false;
|
||||||
|
|
||||||
|
const newTranslations = await Localization.fetchTranslationsFor(newLocale);
|
||||||
|
|
||||||
|
if(!newTranslations) return false;
|
||||||
|
|
||||||
|
const firstTranslation = !Localization.locale
|
||||||
|
|
||||||
|
Localization.locale = newLocale;
|
||||||
|
Localization.translations = newTranslations;
|
||||||
|
|
||||||
|
if (firstTranslation) {
|
||||||
|
Events.fire("translation-loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchTranslationsFor(newLocale) {
|
||||||
|
const response = await fetch(`lang/${newLocale}.json`)
|
||||||
|
|
||||||
|
if (response.redirected === true || response.status !== 200) return false;
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
static translatePage() {
|
||||||
|
document
|
||||||
|
.querySelectorAll("[data-i18n-key]")
|
||||||
|
.forEach(element => Localization.translateElement(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async translateElement(element) {
|
||||||
|
const key = element.getAttribute("data-i18n-key");
|
||||||
|
const attrs = element.getAttribute("data-i18n-attrs").split(" ");
|
||||||
|
|
||||||
|
for (let i in attrs) {
|
||||||
|
let attr = attrs[i];
|
||||||
|
if (attr === "text") {
|
||||||
|
element.innerText = await Localization.getTranslation(key);
|
||||||
|
} else {
|
||||||
|
element.attr = await Localization.getTranslation(key, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTranslation(key, attr, data) {
|
||||||
|
const keys = key.split(".");
|
||||||
|
|
||||||
|
let translationCandidates = Localization.translations;
|
||||||
|
|
||||||
|
for (let i=0; i<keys.length-1; i++) {
|
||||||
|
translationCandidates = translationCandidates[keys[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastKey = keys[keys.length-1];
|
||||||
|
if (attr) lastKey += "_" + attr;
|
||||||
|
|
||||||
|
let translation = translationCandidates[lastKey];
|
||||||
|
|
||||||
|
for (key in data) {
|
||||||
|
translation = translation.replace(`{{${key}}}`, data[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Localization.escapeHTML(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
static escapeHTML(unsafeText) {
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.innerText = unsafeText;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,12 +44,12 @@ class ServerConnection {
|
||||||
_onOpen() {
|
_onOpen() {
|
||||||
console.log('WS: server connected');
|
console.log('WS: server connected');
|
||||||
Events.fire('ws-connected');
|
Events.fire('ws-connected');
|
||||||
if (this._isReconnect) Events.fire('notify-user', 'Connected.');
|
if (this._isReconnect) Events.fire('notify-user', Localization.getTranslation("notifications.connected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPairDeviceInitiate() {
|
_onPairDeviceInitiate() {
|
||||||
if (!this._isConnected()) {
|
if (!this._isConnected()) {
|
||||||
Events.fire('notify-user', 'You need to be online to pair devices.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.online-requirement"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.send({ type: 'pair-device-initiate' })
|
this.send({ type: 'pair-device-initiate' })
|
||||||
|
@ -105,7 +105,7 @@ class ServerConnection {
|
||||||
Events.fire('pair-device-canceled', msg.roomKey);
|
Events.fire('pair-device-canceled', msg.roomKey);
|
||||||
break;
|
break;
|
||||||
case 'pair-device-join-key-rate-limit':
|
case 'pair-device-join-key-rate-limit':
|
||||||
Events.fire('notify-user', 'Rate limit reached. Wait 10 seconds and try again.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.rate-limit-join-key"));
|
||||||
break;
|
break;
|
||||||
case 'secret-room-deleted':
|
case 'secret-room-deleted':
|
||||||
Events.fire('secret-room-deleted', msg.roomSecret);
|
Events.fire('secret-room-deleted', msg.roomSecret);
|
||||||
|
@ -200,7 +200,7 @@ class ServerConnection {
|
||||||
|
|
||||||
_onDisconnect() {
|
_onDisconnect() {
|
||||||
console.log('WS: server disconnected');
|
console.log('WS: server disconnected');
|
||||||
Events.fire('notify-user', 'Connecting..');
|
Events.fire('notify-user', Localization.getTranslation("notifications.connecting"));
|
||||||
clearTimeout(this._reconnectTimer);
|
clearTimeout(this._reconnectTimer);
|
||||||
this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
|
this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
|
||||||
Events.fire('ws-disconnected');
|
Events.fire('ws-disconnected');
|
||||||
|
@ -505,7 +505,7 @@ class Peer {
|
||||||
|
|
||||||
_abortTransfer() {
|
_abortTransfer() {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
||||||
Events.fire('notify-user', 'Files are incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
|
||||||
this._filesReceived = [];
|
this._filesReceived = [];
|
||||||
this._requestAccepted = null;
|
this._requestAccepted = null;
|
||||||
this._digester = null;
|
this._digester = null;
|
||||||
|
@ -546,7 +546,7 @@ class Peer {
|
||||||
this._abortTransfer();
|
this._abortTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// include for compatibility with Snapdrop for Android app
|
// include for compatibility with 'Snapdrop & PairDrop for Android' app
|
||||||
Events.fire('file-received', fileBlob);
|
Events.fire('file-received', fileBlob);
|
||||||
|
|
||||||
this._filesReceived.push(fileBlob);
|
this._filesReceived.push(fileBlob);
|
||||||
|
@ -563,7 +563,8 @@ class Peer {
|
||||||
this._chunker = null;
|
this._chunker = null;
|
||||||
if (!this._filesQueue.length) {
|
if (!this._filesQueue.length) {
|
||||||
this._busy = false;
|
this._busy = false;
|
||||||
Events.fire('notify-user', 'File transfer completed.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
|
||||||
|
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
||||||
} else {
|
} else {
|
||||||
this._dequeueFile();
|
this._dequeueFile();
|
||||||
}
|
}
|
||||||
|
@ -574,7 +575,7 @@ class Peer {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
||||||
this._filesRequested = null;
|
this._filesRequested = null;
|
||||||
if (message.reason === 'ios-memory-limit') {
|
if (message.reason === 'ios-memory-limit') {
|
||||||
Events.fire('notify-user', "Sending files to iOS is only possible up to 200MB at once");
|
Events.fire('notify-user', Localization.getTranslation("notifications.ios-memory-limit"));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -584,7 +585,7 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageTransferCompleted() {
|
_onMessageTransferCompleted() {
|
||||||
Events.fire('notify-user', 'Message transfer completed.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendText(text) {
|
sendText(text) {
|
||||||
|
@ -729,7 +730,7 @@ class RTCPeer extends Peer {
|
||||||
_onBeforeUnload(e) {
|
_onBeforeUnload(e) {
|
||||||
if (this._busy) {
|
if (this._busy) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return "There are unfinished transfers. Are you sure you want to close?";
|
return Localization.getTranslation("notifications.unfinished-transfers-warning");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,12 @@ class PeersUI {
|
||||||
if (newDisplayName) {
|
if (newDisplayName) {
|
||||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
Events.fire('notify-user', 'Device name is changed permanently.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||||
Events.fire('notify-user', 'Device name is changed only for this session.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
Events.fire('self-display-name-changed', newDisplayName);
|
Events.fire('self-display-name-changed', newDisplayName);
|
||||||
|
@ -105,10 +105,9 @@ class PeersUI {
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||||
localStorage.removeItem('editedDisplayName');
|
localStorage.removeItem('editedDisplayName');
|
||||||
Events.fire('notify-user', 'Random Display name is used again.');
|
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
Events.fire('notify-user', 'Device name is randomly generated again.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
|
||||||
Events.fire('self-display-name-changed', '');
|
Events.fire('self-display-name-changed', '');
|
||||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
||||||
});
|
});
|
||||||
|
@ -275,21 +274,22 @@ class PeersUI {
|
||||||
let descriptor;
|
let descriptor;
|
||||||
let noPeersMessage;
|
let noPeersMessage;
|
||||||
|
|
||||||
|
const openPairDrop = Localization.getTranslation("dialogs.activate-paste-mode-base");
|
||||||
|
const andOtherFiles = Localization.getTranslation("dialogs.activate-paste-mode-and-other-files", null, {count: files.length-1});
|
||||||
|
const sharedText = Localization.getTranslation("dialogs.activate-paste-mode-shared-text");
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
descriptor = files[0].name;
|
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i>`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br><i>${descriptor}</i>`;
|
|
||||||
} else if (files.length > 1) {
|
} else if (files.length > 1) {
|
||||||
descriptor = `${files[0].name} and ${files.length-1} other files`;
|
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i> ${andOtherFiles}`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
|
||||||
} else {
|
} else {
|
||||||
descriptor = "shared text";
|
noPeersMessage = `${openPairDrop}<br>${sharedText}`;
|
||||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$xInstructions.querySelector('p').innerHTML = `<i>${descriptor}</i>`;
|
this.$xInstructions.querySelector('p').innerHTML = noPeersMessage;
|
||||||
this.$xInstructions.querySelector('p').style.display = 'block';
|
this.$xInstructions.querySelector('p').style.display = 'block';
|
||||||
this.$xInstructions.setAttribute('desktop', `Click to send`);
|
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.click-to-send"));
|
||||||
this.$xInstructions.setAttribute('mobile', `Tap to send`);
|
this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.tap-to-send"));
|
||||||
|
|
||||||
this.$xNoPeers.querySelector('h2').innerHTML = noPeersMessage;
|
this.$xNoPeers.querySelector('h2').innerHTML = noPeersMessage;
|
||||||
|
|
||||||
|
@ -320,10 +320,10 @@ class PeersUI {
|
||||||
this.$xInstructions.querySelector('p').innerText = '';
|
this.$xInstructions.querySelector('p').innerText = '';
|
||||||
this.$xInstructions.querySelector('p').style.display = 'none';
|
this.$xInstructions.querySelector('p').style.display = 'none';
|
||||||
|
|
||||||
this.$xInstructions.setAttribute('desktop', 'Click to send files or right click to send a message');
|
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.x-instructions", "desktop"));
|
||||||
this.$xInstructions.setAttribute('mobile', 'Tap to send files or long tap to send a message');
|
this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.x-instructions", "mobile"));
|
||||||
|
|
||||||
this.$xNoPeers.querySelector('h2').innerHTML = 'Open PairDrop on other devices to send files';
|
this.$xNoPeers.querySelector('h2').innerHTML = Localization.getTranslation("instructions.no-peers-title");
|
||||||
|
|
||||||
this.$cancelPasteModeBtn.setAttribute('hidden', "");
|
this.$cancelPasteModeBtn.setAttribute('hidden', "");
|
||||||
|
|
||||||
|
@ -368,9 +368,9 @@ class PeerUI {
|
||||||
let title;
|
let title;
|
||||||
let input = '';
|
let input = '';
|
||||||
if (window.pasteMode.activated) {
|
if (window.pasteMode.activated) {
|
||||||
title = `Click to send ${window.pasteMode.descriptor}`;
|
title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor});
|
||||||
} else {
|
} else {
|
||||||
title = 'Click to send files or right click to send a message';
|
title = Localization.getTranslation("peer-ui.click-to-send");
|
||||||
input = '<input type="file" multiple>';
|
input = '<input type="file" multiple>';
|
||||||
}
|
}
|
||||||
this.$el.innerHTML = `
|
this.$el.innerHTML = `
|
||||||
|
@ -392,7 +392,7 @@ class PeerUI {
|
||||||
<div class="name font-subheading"></div>
|
<div class="name font-subheading"></div>
|
||||||
<div class="device-name font-body2"></div>
|
<div class="device-name font-body2"></div>
|
||||||
<div class="status font-body2"></div>
|
<div class="status font-body2"></div>
|
||||||
<span class="connection-hash font-body2" title="To verify the security of the end-to-end encryption, compare this security number on both devices"></span>
|
<span class="connection-hash font-body2" title="${ Localization.getTranslation("peer-ui.connection-hash") }"></span>
|
||||||
</div>
|
</div>
|
||||||
</label>`;
|
</label>`;
|
||||||
|
|
||||||
|
@ -510,10 +510,23 @@ class PeerUI {
|
||||||
$progress.classList.remove('over50');
|
$progress.classList.remove('over50');
|
||||||
}
|
}
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
this.$el.setAttribute('status', status);
|
if (status !== this.currentStatus) {
|
||||||
|
let statusName = {
|
||||||
|
"prepare": Localization.getTranslation("peer-ui.preparing"),
|
||||||
|
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
||||||
|
"process": Localization.getTranslation("peer-ui.processing"),
|
||||||
|
"wait": Localization.getTranslation("peer-ui.waiting")
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
this.$el.setAttribute('status', status);
|
||||||
|
this.$el.querySelector('.status').innerText = statusName;
|
||||||
|
this.currentStatus = status;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$el.removeAttribute('status');
|
this.$el.removeAttribute('status');
|
||||||
|
this.$el.querySelector('.status').innerHTML = '';
|
||||||
progress = 0;
|
progress = 0;
|
||||||
|
this.currentStatus = null;
|
||||||
}
|
}
|
||||||
const degrees = `rotate(${360 * progress}deg)`;
|
const degrees = `rotate(${360 * progress}deg)`;
|
||||||
$progress.style.setProperty('--progress', degrees);
|
$progress.style.setProperty('--progress', degrees);
|
||||||
|
@ -596,7 +609,7 @@ class Dialog {
|
||||||
_onPeerDisconnected(peerId) {
|
_onPeerDisconnected(peerId) {
|
||||||
if (this.isShown() && this.correspondingPeerId === peerId) {
|
if (this.isShown() && this.correspondingPeerId === peerId) {
|
||||||
this.hide();
|
this.hide();
|
||||||
Events.fire('notify-user', 'Selected peer left.')
|
Events.fire('notify-user', Localization.getTranslation("notifications.selected-peer-left"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,13 +643,17 @@ class ReceiveDialog extends Dialog {
|
||||||
|
|
||||||
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||||
if (files.length > 1) {
|
if (files.length > 1) {
|
||||||
let fileOtherText = ` and ${files.length - 1} other `;
|
let fileOther;
|
||||||
if (files.length === 2) {
|
if (files.length === 2) {
|
||||||
fileOtherText += imagesOnly ? 'image' : 'file';
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||||
} else {
|
} else {
|
||||||
fileOtherText += imagesOnly ? 'images' : 'files';
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||||
}
|
}
|
||||||
this.$fileOther.innerText = fileOtherText;
|
this.$fileOther.innerText = fileOther;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = files[0].name;
|
const fileName = files[0].name;
|
||||||
|
@ -728,11 +745,15 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
|
|
||||||
let descriptor, url, filenameDownload;
|
let descriptor, url, filenameDownload;
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
descriptor = imagesOnly ? 'Image' : 'File';
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image")
|
||||||
|
: Localization.getTranslation("dialogs.title-file");
|
||||||
} else {
|
} else {
|
||||||
descriptor = imagesOnly ? 'Images' : 'Files';
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image-plural")
|
||||||
|
: Localization.getTranslation("dialogs.title-file-plural");
|
||||||
}
|
}
|
||||||
this.$receiveTitle.innerText = `${descriptor} Received`;
|
this.$receiveTitle.innerText = Localization.getTranslation("dialogs.receive-title", null, {descriptor: descriptor});
|
||||||
|
|
||||||
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
|
@ -782,7 +803,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$downloadBtn.innerText = "Download";
|
this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download");
|
||||||
this.$downloadBtn.onclick = _ => {
|
this.$downloadBtn.onclick = _ => {
|
||||||
if (downloadZipped) {
|
if (downloadZipped) {
|
||||||
let tmpZipBtn = document.createElement("a");
|
let tmpZipBtn = document.createElement("a");
|
||||||
|
@ -794,17 +815,18 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canShare) {
|
if (!canShare) {
|
||||||
this.$downloadBtn.innerText = "Download again";
|
this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download-again");
|
||||||
}
|
}
|
||||||
Events.fire('notify-user', `${descriptor} downloaded successfully`);
|
Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor}));
|
||||||
this.$downloadBtn.style.pointerEvents = "none";
|
this.$downloadBtn.style.pointerEvents = "none";
|
||||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.title = files.length === 1
|
document.title = files.length === 1
|
||||||
? 'File received - PairDrop'
|
? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
|
||||||
: `${files.length} Files received - PairDrop`;
|
: `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
|
|
||||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||||
this.show();
|
this.show();
|
||||||
|
|
||||||
|
@ -892,7 +914,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
||||||
|
|
||||||
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
||||||
|
|
||||||
document.title = `${request.imagesOnly ? 'Image' : 'File'} Transfer Requested - PairDrop`;
|
document.title = `${ Localization.getTranslation("document-titles.file-transfer-requested") } - PairDrop`;
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
this.show();
|
this.show();
|
||||||
}
|
}
|
||||||
|
@ -1084,7 +1106,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
this.hide();
|
this.hide();
|
||||||
Events.fire('notify-user', 'Pairing of two browser tabs is not possible.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-tabs-error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1130,7 +1152,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
|
|
||||||
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
Events.fire('notify-user', 'Devices paired successfully.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
|
||||||
this._evaluateNumberRoomSecrets();
|
this._evaluateNumberRoomSecrets();
|
||||||
})
|
})
|
||||||
.finally(_ => {
|
.finally(_ => {
|
||||||
|
@ -1138,13 +1160,13 @@ class PairDeviceDialog extends Dialog {
|
||||||
this.hide();
|
this.hide();
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Paired devices are not persistent.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-not-persistent"));
|
||||||
PersistentStorage.logBrowserNotCapable();
|
PersistentStorage.logBrowserNotCapable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceJoinKeyInvalid() {
|
_pairDeviceJoinKeyInvalid() {
|
||||||
Events.fire('notify-user', 'Key not valid');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceCancel() {
|
_pairDeviceCancel() {
|
||||||
|
@ -1154,7 +1176,7 @@ class PairDeviceDialog extends Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_pairDeviceCanceled(roomKey) {
|
_pairDeviceCanceled(roomKey) {
|
||||||
Events.fire('notify-user', `Key ${roomKey} invalidated.`);
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalidated", null, {key: roomKey}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanUp() {
|
_cleanUp() {
|
||||||
|
@ -1261,7 +1283,7 @@ class EditPairedDevicesDialog extends Dialog {
|
||||||
PersistentStorage.clearRoomSecrets().finally(_ => {
|
PersistentStorage.clearRoomSecrets().finally(_ => {
|
||||||
Events.fire('room-secrets-deleted', roomSecrets);
|
Events.fire('room-secrets-deleted', roomSecrets);
|
||||||
Events.fire('evaluate-number-room-secrets');
|
Events.fire('evaluate-number-room-secrets');
|
||||||
Events.fire('notify-user', 'All Devices unpaired.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
|
||||||
this.hide();
|
this.hide();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -1416,14 +1438,14 @@ class ReceiveTextDialog extends Dialog {
|
||||||
|
|
||||||
_setDocumentTitleMessages() {
|
_setDocumentTitleMessages() {
|
||||||
document.title = !this._receiveTextQueue.length
|
document.title = !this._receiveTextQueue.length
|
||||||
? 'Message Received - PairDrop'
|
? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
|
||||||
: `${this._receiveTextQueue.length + 1} Messages Received - PairDrop`;
|
: `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onCopy() {
|
async _onCopy() {
|
||||||
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
||||||
await navigator.clipboard.writeText(sanitizedText);
|
await navigator.clipboard.writeText(sanitizedText);
|
||||||
Events.fire('notify-user', 'Copied to clipboard');
|
Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard"));
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1450,13 +1472,13 @@ class Base64ZipDialog extends Dialog {
|
||||||
if (base64Text === "paste") {
|
if (base64Text === "paste") {
|
||||||
// ?base64text=paste
|
// ?base64text=paste
|
||||||
// base64 encoded string is ready to be pasted from clipboard
|
// base64 encoded string is ready to be pasted from clipboard
|
||||||
this.preparePasting("text");
|
this.preparePasting(Localization.getTranslation("dialogs.base64-text"));
|
||||||
} else if (base64Text === "hash") {
|
} else if (base64Text === "hash") {
|
||||||
// ?base64text=hash#BASE64ENCODED
|
// ?base64text=hash#BASE64ENCODED
|
||||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||||
this.processBase64Text(base64Hash)
|
this.processBase64Text(base64Hash)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Text content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||||
console.log("Text content incorrect.");
|
console.log("Text content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1466,7 +1488,7 @@ class Base64ZipDialog extends Dialog {
|
||||||
// base64 encoded string was part of url param (not recommended)
|
// base64 encoded string was part of url param (not recommended)
|
||||||
this.processBase64Text(base64Text)
|
this.processBase64Text(base64Text)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'Text content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||||
console.log("Text content incorrect.");
|
console.log("Text content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1479,32 +1501,32 @@ class Base64ZipDialog extends Dialog {
|
||||||
// base64 encoded zip file is url hash which is never sent to the server
|
// base64 encoded zip file is url hash which is never sent to the server
|
||||||
this.processBase64Zip(base64Hash)
|
this.processBase64Zip(base64Hash)
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
Events.fire('notify-user', 'File content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
|
||||||
console.log("File content incorrect.");
|
console.log("File content incorrect.");
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.hide();
|
this.hide();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// ?base64zip=paste || ?base64zip=true
|
// ?base64zip=paste || ?base64zip=true
|
||||||
this.preparePasting('files');
|
this.preparePasting(Localization.getTranslation("dialogs.base64-files"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setPasteBtnToProcessing() {
|
_setPasteBtnToProcessing() {
|
||||||
this.$pasteBtn.style.pointerEvents = "none";
|
this.$pasteBtn.style.pointerEvents = "none";
|
||||||
this.$pasteBtn.innerText = "Processing...";
|
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing");
|
||||||
}
|
}
|
||||||
|
|
||||||
preparePasting(type) {
|
preparePasting(type) {
|
||||||
if (navigator.clipboard.readText) {
|
if (navigator.clipboard.readText) {
|
||||||
this.$pasteBtn.innerText = `Tap here to paste ${type}`;
|
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", {type: type});
|
||||||
this._clickCallback = _ => this.processClipboard(type);
|
this._clickCallback = _ => this.processClipboard(type);
|
||||||
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
||||||
} else {
|
} else {
|
||||||
console.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.")
|
console.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.")
|
||||||
this.$pasteBtn.setAttribute('hidden', '');
|
this.$pasteBtn.setAttribute('hidden', '');
|
||||||
this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`);
|
this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", {type: type}));
|
||||||
this.$fallbackTextarea.removeAttribute('hidden');
|
this.$fallbackTextarea.removeAttribute('hidden');
|
||||||
this._inputCallback = _ => this.processInput(type);
|
this._inputCallback = _ => this.processInput(type);
|
||||||
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
||||||
|
@ -1544,7 +1566,7 @@ class Base64ZipDialog extends Dialog {
|
||||||
await this.processBase64Zip(base64);
|
await this.processBase64Zip(base64);
|
||||||
}
|
}
|
||||||
} catch(_) {
|
} catch(_) {
|
||||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.clipboard-content-incorrect"));
|
||||||
console.log("Clipboard content is incorrect.")
|
console.log("Clipboard content is incorrect.")
|
||||||
}
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -1627,7 +1649,7 @@ class Notifications {
|
||||||
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
|
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Events.fire('notify-user', 'Notifications enabled.');
|
Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
|
||||||
this.$button.setAttribute('hidden', 1);
|
this.$button.setAttribute('hidden', 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1662,10 +1684,10 @@ class Notifications {
|
||||||
if (document.visibilityState !== 'visible') {
|
if (document.visibilityState !== 'visible') {
|
||||||
const peerDisplayName = $(peerId).ui._displayName();
|
const peerDisplayName = $(peerId).ui._displayName();
|
||||||
if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
|
if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
|
||||||
const notification = this._notify(`Link received by ${peerDisplayName} - Click to open`, message);
|
const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message);
|
||||||
this._bind(notification, _ => window.open(message, '_blank', null, true));
|
this._bind(notification, _ => window.open(message, '_blank', null, true));
|
||||||
} else {
|
} else {
|
||||||
const notification = this._notify(`Message received by ${peerDisplayName} - Click to copy`, message);
|
const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message);
|
||||||
this._bind(notification, _ => this._copyText(message, notification));
|
this._bind(notification, _ => this._copyText(message, notification));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1680,13 +1702,23 @@ class Notifications {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let title = files[0].name;
|
let title;
|
||||||
if (files.length >= 2) {
|
if (files.length === 1) {
|
||||||
title += ` and ${files.length - 1} other `;
|
title = `${files[0].name}`;
|
||||||
title += imagesOnly ? 'image' : 'file';
|
} else {
|
||||||
if (files.length > 2) title += "s";
|
let fileOther;
|
||||||
|
if (files.length === 2) {
|
||||||
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||||
|
} else {
|
||||||
|
fileOther = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||||
|
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||||
|
}
|
||||||
|
title = `${files[0].name} ${fileOther}`
|
||||||
}
|
}
|
||||||
const notification = this._notify(title, 'Click to download');
|
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-download"));
|
||||||
this._bind(notification, _ => this._download(notification));
|
this._bind(notification, _ => this._download(notification));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1700,15 +1732,27 @@ class Notifications {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let descriptor;
|
|
||||||
if (request.header.length > 1) {
|
|
||||||
descriptor = imagesOnly ? ' images' : ' files';
|
|
||||||
} else {
|
|
||||||
descriptor = imagesOnly ? ' image' : ' file';
|
|
||||||
}
|
|
||||||
let displayName = $(peerId).querySelector('.name').textContent
|
let displayName = $(peerId).querySelector('.name').textContent
|
||||||
let title = `${displayName} would like to transfer ${request.header.length} ${descriptor}`;
|
|
||||||
const notification = this._notify(title, 'Click to show');
|
let descriptor;
|
||||||
|
if (request.header.length === 1) {
|
||||||
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image")
|
||||||
|
: Localization.getTranslation("dialogs.title-file");
|
||||||
|
} else {
|
||||||
|
descriptor = imagesOnly
|
||||||
|
? Localization.getTranslation("dialogs.title-image-plural")
|
||||||
|
: Localization.getTranslation("dialogs.title-file-plural");
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = Localization.getTranslation("notifications.request-title", null, {
|
||||||
|
name: displayName,
|
||||||
|
count: request.header.length,
|
||||||
|
descriptor: descriptor.toLowerCase()
|
||||||
|
});
|
||||||
|
|
||||||
|
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1720,10 +1764,9 @@ class Notifications {
|
||||||
_copyText(message, notification) {
|
_copyText(message, notification) {
|
||||||
if (navigator.clipboard.writeText(message)) {
|
if (navigator.clipboard.writeText(message)) {
|
||||||
notification.close();
|
notification.close();
|
||||||
this._notify('Copied text to clipboard');
|
this._notify(Localization.getTranslation("notifications.copied-text"));
|
||||||
} else {
|
} else {
|
||||||
this._notify('Writing to clipboard failed. Copy manually!');
|
this._notify(Localization.getTranslation("notifications.copied-text-error"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1747,11 +1790,11 @@ class NetworkStatusUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showOfflineMessage() {
|
_showOfflineMessage() {
|
||||||
Events.fire('notify-user', 'You are offline');
|
Events.fire('notify-user', Localization.getTranslation("notifications.offline"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_showOnlineMessage() {
|
_showOnlineMessage() {
|
||||||
Events.fire('notify-user', 'You are back online');
|
Events.fire('notify-user', Localization.getTranslation("notifications.online"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2209,7 +2252,7 @@ class BrowserTabsConnector {
|
||||||
|
|
||||||
class PairDrop {
|
class PairDrop {
|
||||||
constructor() {
|
constructor() {
|
||||||
Events.on('load', _ => {
|
Events.on('translation-loaded', _ => {
|
||||||
const server = new ServerConnection();
|
const server = new ServerConnection();
|
||||||
const peers = new PeersManager(server);
|
const peers = new PeersManager(server);
|
||||||
const peersUI = new PeersUI();
|
const peersUI = new PeersUI();
|
||||||
|
@ -2233,6 +2276,7 @@ class PairDrop {
|
||||||
|
|
||||||
const persistentStorage = new PersistentStorage();
|
const persistentStorage = new PersistentStorage();
|
||||||
const pairDrop = new PairDrop();
|
const pairDrop = new PairDrop();
|
||||||
|
const localization = new Localization();
|
||||||
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
|
|
|
@ -1345,11 +1345,11 @@ x-peers:empty~x-instructions {
|
||||||
transition: opacity 300ms;
|
transition: opacity 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
#websocket-fallback > span {
|
#websocket-fallback {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#websocket-fallback > span > span {
|
#websocket-fallback > span:nth-child(2) {
|
||||||
border-bottom: solid 4px var(--ws-peer-color);
|
border-bottom: solid 4px var(--ws-peer-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue