mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-26 09:46:19 -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">
|
||||
<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">
|
||||
<use xlink:href="#info-outline" />
|
||||
</svg>
|
||||
</a>
|
||||
<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">
|
||||
<use xlink:href="#icon-theme-auto" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#icon-theme-light" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#icon-theme-dark" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#notifications" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#homescreen" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#pair-device-icon" />
|
||||
</svg>
|
||||
</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">
|
||||
<use xlink:href="#edit-pair-devices-icon" />
|
||||
</svg>
|
||||
</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>
|
||||
<!-- Center -->
|
||||
<div id="center">
|
||||
<!-- Peers -->
|
||||
<div class="x-peers-filler"></div>
|
||||
<x-peers class="center"></x-peers>
|
||||
<x-no-peers>
|
||||
<h2>Open PairDrop on other devices to send files</h2>
|
||||
<div>Pair devices to be discoverable on other networks</div>
|
||||
<x-no-peers data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg" data-drop-bg="Release to select recipient">
|
||||
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text">Open PairDrop on other devices to send files</h2>
|
||||
<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-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>
|
||||
</x-instructions>
|
||||
</div>
|
||||
|
@ -104,15 +108,21 @@
|
|||
<use xlink:href="#wifi-tethering" />
|
||||
</svg>
|
||||
<div>
|
||||
<span>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>
|
||||
<span data-i18n-key="footer.known-as" data-i18n-attrs="text">You are known as:</span>
|
||||
<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">
|
||||
<use xlink:href="#edit-pen-icon" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="font-body2">
|
||||
You can be discovered by everyone <span id="on-this-network">on this network</span>
|
||||
<span id="and-by-paired-devices" hidden> and by <span id="paired-devices">paired devices</span></span>
|
||||
<div>
|
||||
<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>
|
||||
</footer>
|
||||
<!-- Pair Device Dialog -->
|
||||
|
@ -120,10 +130,13 @@
|
|||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -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-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
</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">
|
||||
<button class="button" type="submit" disabled>Pair</button>
|
||||
<button class="button" type="button" close>Cancel</button>
|
||||
<button class="button" type="submit" data-i18n-key="dialogs.pair" data-i18n-attrs="text" disabled>Pair</button>
|
||||
<button class="button" type="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -147,13 +160,21 @@
|
|||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="center">Edit Paired Devices</h2>
|
||||
<div class="paired-devices-wrapper"></div>
|
||||
<h2 class="center" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text">Edit Paired Devices</h2>
|
||||
<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">
|
||||
<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 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>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -167,7 +188,7 @@
|
|||
<div class="center column file-description">
|
||||
<div>
|
||||
<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 class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
|
@ -179,8 +200,8 @@
|
|||
</div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="center row-reverse">
|
||||
<button id="accept-request" class="button" title="ENTER" autofocus>Accept</button>
|
||||
<button id="decline-request" class="button" title="ESCAPE">Decline</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" data-i18n-key="dialogs.decline" data-i18n-attrs="text">Decline</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -193,7 +214,7 @@
|
|||
<div class="center column file-description">
|
||||
<div>
|
||||
<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 class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
|
@ -204,9 +225,9 @@
|
|||
</div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="center row-reverse">
|
||||
<button id="share-btn" class="button" autofocus hidden>Share</button>
|
||||
<button id="download-btn" class="button" autofocus>Download</button>
|
||||
<button class="button" close>Close</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" data-i18n-key="dialogs.download" data-i18n-attrs="text" autofocus>Download</button>
|
||||
<button class="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close>Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -216,16 +237,16 @@
|
|||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
|
||||
<button class="button" type="button" title="ESCAPE" close>Cancel</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" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -235,16 +256,16 @@
|
|||
<x-dialog id="receive-text-dialog">
|
||||
<x-background class="full center">
|
||||
<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">
|
||||
<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 class="row-separator"></div>
|
||||
<div id="text"></div>
|
||||
<div class="center row-reverse">
|
||||
<button id="copy" class="button" title="CTRL/⌘ + C">Copy</button>
|
||||
<button id="close" class="button" title="ESCAPE">Close</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" data-i18n-key="dialogs.close" data-i18n-attrs="text">Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -253,9 +274,9 @@
|
|||
<x-dialog id="base64-paste-dialog">
|
||||
<x-background class="full center">
|
||||
<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>
|
||||
<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-background>
|
||||
</x-dialog>
|
||||
|
@ -266,7 +287,7 @@
|
|||
<!-- About Page -->
|
||||
<x-about id="about" class="full center column">
|
||||
<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">
|
||||
<use xlink:href="#close-icon" />
|
||||
</svg>
|
||||
|
@ -280,7 +301,7 @@
|
|||
<h1>PairDrop</h1>
|
||||
<div class="font-subheading">v1.7.6</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">
|
||||
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
||||
<svg class="icon">
|
||||
|
@ -373,6 +394,7 @@
|
|||
</symbol>
|
||||
</svg>
|
||||
<!-- Scripts -->
|
||||
<script src="scripts/localization.js"></script>
|
||||
<script src="scripts/theme.js"></script>
|
||||
<script src="scripts/network.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() {
|
||||
console.log('WS: server 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() {
|
||||
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;
|
||||
}
|
||||
this.send({ type: 'pair-device-initiate' })
|
||||
|
@ -107,7 +107,7 @@ class ServerConnection {
|
|||
Events.fire('pair-device-canceled', msg.roomKey);
|
||||
break;
|
||||
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;
|
||||
case 'secret-room-deleted':
|
||||
Events.fire('secret-room-deleted', msg.roomSecret);
|
||||
|
@ -183,7 +183,7 @@ class ServerConnection {
|
|||
|
||||
_onDisconnect() {
|
||||
console.log('WS: server disconnected');
|
||||
Events.fire('notify-user', 'Connecting..');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.connecting"));
|
||||
clearTimeout(this._reconnectTimer);
|
||||
this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
|
||||
Events.fire('ws-disconnected');
|
||||
|
@ -488,7 +488,7 @@ class Peer {
|
|||
|
||||
_abortTransfer() {
|
||||
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._requestAccepted = null;
|
||||
this._digester = null;
|
||||
|
@ -546,7 +546,7 @@ class Peer {
|
|||
this._chunker = null;
|
||||
if (!this._filesQueue.length) {
|
||||
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 {
|
||||
this._dequeueFile();
|
||||
|
@ -558,7 +558,7 @@ class Peer {
|
|||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
||||
this._filesRequested = null;
|
||||
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;
|
||||
}
|
||||
|
@ -568,7 +568,7 @@ class Peer {
|
|||
}
|
||||
|
||||
_onMessageTransferCompleted() {
|
||||
Events.fire('notify-user', 'Message transfer completed.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
||||
}
|
||||
|
||||
sendText(text) {
|
||||
|
@ -713,7 +713,7 @@ class RTCPeer extends Peer {
|
|||
_onBeforeUnload(e) {
|
||||
if (this._busy) {
|
||||
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) {
|
||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', 'Device name is changed permanently.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
|
||||
})
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||
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(_ => {
|
||||
Events.fire('self-display-name-changed', newDisplayName);
|
||||
|
@ -105,10 +105,9 @@ class PeersUI {
|
|||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||
localStorage.removeItem('editedDisplayName');
|
||||
Events.fire('notify-user', 'Random Display name is used again.');
|
||||
})
|
||||
.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('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
||||
});
|
||||
|
@ -275,21 +274,22 @@ class PeersUI {
|
|||
let descriptor;
|
||||
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) {
|
||||
descriptor = files[0].name;
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br><i>${descriptor}</i>`;
|
||||
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i>`;
|
||||
} else if (files.length > 1) {
|
||||
descriptor = `${files[0].name} and ${files.length-1} other files`;
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
noPeersMessage = `${openPairDrop}<br><i>${files[0].name}</i> ${andOtherFiles}`;
|
||||
} else {
|
||||
descriptor = "shared text";
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
noPeersMessage = `${openPairDrop}<br>${sharedText}`;
|
||||
}
|
||||
|
||||
this.$xInstructions.querySelector('p').innerHTML = `<i>${descriptor}</i>`;
|
||||
this.$xInstructions.querySelector('p').innerHTML = noPeersMessage;
|
||||
this.$xInstructions.querySelector('p').style.display = 'block';
|
||||
this.$xInstructions.setAttribute('desktop', `Click to send`);
|
||||
this.$xInstructions.setAttribute('mobile', `Tap to send`);
|
||||
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.click-to-send"));
|
||||
this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.tap-to-send"));
|
||||
|
||||
this.$xNoPeers.querySelector('h2').innerHTML = noPeersMessage;
|
||||
|
||||
|
@ -320,10 +320,10 @@ class PeersUI {
|
|||
this.$xInstructions.querySelector('p').innerText = '';
|
||||
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('mobile', 'Tap to send files or long tap to send a message');
|
||||
this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.x-instructions", "desktop"));
|
||||
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', "");
|
||||
|
||||
|
@ -368,9 +368,9 @@ class PeerUI {
|
|||
let title;
|
||||
let input = '';
|
||||
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 {
|
||||
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>';
|
||||
}
|
||||
this.$el.innerHTML = `
|
||||
|
@ -392,7 +392,7 @@ class PeerUI {
|
|||
<div class="name font-subheading"></div>
|
||||
<div class="device-name 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>
|
||||
</label>`;
|
||||
|
||||
|
@ -509,10 +509,23 @@ class PeerUI {
|
|||
$progress.classList.remove('over50');
|
||||
}
|
||||
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 {
|
||||
this.$el.removeAttribute('status');
|
||||
this.$el.querySelector('.status').innerHTML = '';
|
||||
progress = 0;
|
||||
this.currentStatus = null;
|
||||
}
|
||||
const degrees = `rotate(${360 * progress}deg)`;
|
||||
$progress.style.setProperty('--progress', degrees);
|
||||
|
@ -595,7 +608,7 @@ class Dialog {
|
|||
_onPeerDisconnected(peerId) {
|
||||
if (this.isShown() && this.correspondingPeerId === peerId) {
|
||||
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) {
|
||||
if (files.length > 1) {
|
||||
let fileOtherText = ` and ${files.length - 1} other `;
|
||||
let fileOther;
|
||||
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 {
|
||||
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;
|
||||
|
@ -727,11 +744,15 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
|
||||
let descriptor, url, filenameDownload;
|
||||
if (files.length === 1) {
|
||||
descriptor = imagesOnly ? 'Image' : 'File';
|
||||
descriptor = imagesOnly
|
||||
? Localization.getTranslation("dialogs.title-image")
|
||||
: Localization.getTranslation("dialogs.title-file");
|
||||
} 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});
|
||||
if (canShare) {
|
||||
|
@ -781,7 +802,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
}
|
||||
}
|
||||
|
||||
this.$downloadBtn.innerText = "Download";
|
||||
this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download");
|
||||
this.$downloadBtn.onclick = _ => {
|
||||
if (downloadZipped) {
|
||||
let tmpZipBtn = document.createElement("a");
|
||||
|
@ -793,17 +814,18 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
}
|
||||
|
||||
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";
|
||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||
};
|
||||
|
||||
document.title = files.length === 1
|
||||
? 'File received - PairDrop'
|
||||
: `${files.length} Files received - PairDrop`;
|
||||
? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
|
||||
: `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
|
||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||
|
||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||
this.show();
|
||||
|
||||
|
@ -891,7 +913,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
|
||||
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");
|
||||
this.show();
|
||||
}
|
||||
|
@ -1083,7 +1105,7 @@ class PairDeviceDialog extends Dialog {
|
|||
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
||||
this._cleanUp();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1129,7 +1151,7 @@ class PairDeviceDialog extends Dialog {
|
|||
|
||||
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', 'Devices paired successfully.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
|
||||
this._evaluateNumberRoomSecrets();
|
||||
})
|
||||
.finally(_ => {
|
||||
|
@ -1137,13 +1159,13 @@ class PairDeviceDialog extends Dialog {
|
|||
this.hide();
|
||||
})
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Paired devices are not persistent.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-not-persistent"));
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
});
|
||||
}
|
||||
|
||||
_pairDeviceJoinKeyInvalid() {
|
||||
Events.fire('notify-user', 'Key not valid');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalid"));
|
||||
}
|
||||
|
||||
_pairDeviceCancel() {
|
||||
|
@ -1153,7 +1175,7 @@ class PairDeviceDialog extends Dialog {
|
|||
}
|
||||
|
||||
_pairDeviceCanceled(roomKey) {
|
||||
Events.fire('notify-user', `Key ${roomKey} invalidated.`);
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalidated", null, {key: roomKey}));
|
||||
}
|
||||
|
||||
_cleanUp() {
|
||||
|
@ -1260,7 +1282,7 @@ class EditPairedDevicesDialog extends Dialog {
|
|||
PersistentStorage.clearRoomSecrets().finally(_ => {
|
||||
Events.fire('room-secrets-deleted', roomSecrets);
|
||||
Events.fire('evaluate-number-room-secrets');
|
||||
Events.fire('notify-user', 'All Devices unpaired.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
|
||||
this.hide();
|
||||
})
|
||||
});
|
||||
|
@ -1415,14 +1437,14 @@ class ReceiveTextDialog extends Dialog {
|
|||
|
||||
_setDocumentTitleMessages() {
|
||||
document.title = !this._receiveTextQueue.length
|
||||
? 'Message Received - PairDrop'
|
||||
: `${this._receiveTextQueue.length + 1} Messages Received - PairDrop`;
|
||||
? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
|
||||
: `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
|
||||
}
|
||||
|
||||
async _onCopy() {
|
||||
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
||||
await navigator.clipboard.writeText(sanitizedText);
|
||||
Events.fire('notify-user', 'Copied to clipboard');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard"));
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
@ -1449,13 +1471,13 @@ class Base64ZipDialog extends Dialog {
|
|||
if (base64Text === "paste") {
|
||||
// ?base64text=paste
|
||||
// base64 encoded string is ready to be pasted from clipboard
|
||||
this.preparePasting("text");
|
||||
this.preparePasting(Localization.getTranslation("dialogs.base64-text"));
|
||||
} else if (base64Text === "hash") {
|
||||
// ?base64text=hash#BASE64ENCODED
|
||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||
this.processBase64Text(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
|
@ -1465,7 +1487,7 @@ class Base64ZipDialog extends Dialog {
|
|||
// base64 encoded string was part of url param (not recommended)
|
||||
this.processBase64Text(base64Text)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
|
@ -1478,32 +1500,32 @@ class Base64ZipDialog extends Dialog {
|
|||
// base64 encoded zip file is url hash which is never sent to the server
|
||||
this.processBase64Zip(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'File content is incorrect.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
|
||||
console.log("File content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64zip=paste || ?base64zip=true
|
||||
this.preparePasting('files');
|
||||
this.preparePasting(Localization.getTranslation("dialogs.base64-files"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setPasteBtnToProcessing() {
|
||||
this.$pasteBtn.style.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing");
|
||||
}
|
||||
|
||||
preparePasting(type) {
|
||||
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.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
||||
} 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.")
|
||||
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._inputCallback = _ => this.processInput(type);
|
||||
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
||||
|
@ -1543,7 +1565,7 @@ class Base64ZipDialog extends Dialog {
|
|||
await this.processBase64Zip(base64);
|
||||
}
|
||||
} 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.")
|
||||
}
|
||||
this.hide();
|
||||
|
@ -1626,7 +1648,7 @@ class Notifications {
|
|||
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
|
||||
return;
|
||||
}
|
||||
Events.fire('notify-user', 'Notifications enabled.');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
|
||||
this.$button.setAttribute('hidden', 1);
|
||||
});
|
||||
}
|
||||
|
@ -1661,10 +1683,10 @@ class Notifications {
|
|||
if (document.visibilityState !== 'visible') {
|
||||
const peerDisplayName = $(peerId).ui._displayName();
|
||||
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));
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
|
@ -1679,13 +1701,23 @@ class Notifications {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let title = files[0].name;
|
||||
if (files.length >= 2) {
|
||||
title += ` and ${files.length - 1} other `;
|
||||
title += imagesOnly ? 'image' : 'file';
|
||||
if (files.length > 2) title += "s";
|
||||
let title;
|
||||
if (files.length === 1) {
|
||||
title = `${files[0].name}`;
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -1699,15 +1731,27 @@ class Notifications {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let descriptor;
|
||||
if (request.header.length > 1) {
|
||||
descriptor = imagesOnly ? ' images' : ' files';
|
||||
} else {
|
||||
descriptor = imagesOnly ? ' image' : ' file';
|
||||
}
|
||||
|
||||
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) {
|
||||
if (navigator.clipboard.writeText(message)) {
|
||||
notification.close();
|
||||
this._notify('Copied text to clipboard');
|
||||
this._notify(Localization.getTranslation("notifications.copied-text"));
|
||||
} else {
|
||||
this._notify('Writing to clipboard failed. Copy manually!');
|
||||
|
||||
this._notify(Localization.getTranslation("notifications.copied-text-error"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1746,11 +1789,11 @@ class NetworkStatusUI {
|
|||
}
|
||||
|
||||
_showOfflineMessage() {
|
||||
Events.fire('notify-user', 'You are offline');
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.offline"));
|
||||
}
|
||||
|
||||
_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 {
|
||||
constructor() {
|
||||
Events.on('load', _ => {
|
||||
Events.on('translation-loaded', _ => {
|
||||
const server = new ServerConnection();
|
||||
const peers = new PeersManager(server);
|
||||
const peersUI = new PeersUI();
|
||||
|
@ -2232,6 +2275,7 @@ class PairDrop {
|
|||
|
||||
const persistentStorage = new PersistentStorage();
|
||||
const pairDrop = new PairDrop();
|
||||
const localization = new Localization();
|
||||
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
|
|
|
@ -442,7 +442,7 @@ x-no-peers::before {
|
|||
}
|
||||
|
||||
x-no-peers[drop-bg]::before {
|
||||
content: "Release to select recipient";
|
||||
content: attr(data-drop-bg);
|
||||
}
|
||||
|
||||
x-no-peers[drop-bg] * {
|
||||
|
@ -553,22 +553,6 @@ x-peer[status] x-icon {
|
|||
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[status] .device-name {
|
||||
display: none;
|
||||
|
@ -626,11 +610,13 @@ footer .font-body2 {
|
|||
#on-this-network {
|
||||
border-bottom: solid 4px var(--primary-color);
|
||||
padding-bottom: 1px;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
#paired-devices {
|
||||
border-bottom: solid 4px var(--paired-device-color);
|
||||
padding-bottom: 1px;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
#display-name {
|
||||
|
@ -723,10 +709,6 @@ x-dialog a {
|
|||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
x-dialog .font-subheading {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Pair Devices Dialog */
|
||||
|
||||
#key-input-container {
|
||||
|
@ -774,6 +756,10 @@ x-dialog .font-subheading {
|
|||
margin: 16px;
|
||||
}
|
||||
|
||||
#pair-instructions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
x-dialog hr {
|
||||
margin: 40px -24px 30px -24px;
|
||||
border: solid 1.25px var(--border-color);
|
||||
|
@ -785,7 +771,7 @@ x-dialog hr {
|
|||
|
||||
/* Edit Paired Devices Dialog */
|
||||
.paired-devices-wrapper:empty:before {
|
||||
content: "No paired devices.";
|
||||
content: attr(data-empty);
|
||||
}
|
||||
|
||||
.paired-devices-wrapper:empty {
|
||||
|
@ -1288,11 +1274,11 @@ x-instructions:not([drop-peer]):not([drop-bg]):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 {
|
||||
content: "Release to select recipient";
|
||||
content: attr(data-drop-bg);
|
||||
}
|
||||
|
||||
x-instructions p {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue