aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2019-02-18 12:48:43 -0800
committerGitHub <noreply@github.com>2019-02-18 12:48:43 -0800
commitf0e0762bce78393b55f596f42d305a0699acd55c (patch)
tree1d8f40906c94f5521955b6bc34b7867f853f6ef3
parent833cc345f02bfc686880ca4af52480d500130f8b (diff)
parent8e200cd8b031225e13a309359142ec4442f772a0 (diff)
downloadonionshare-f0e0762bce78393b55f596f42d305a0699acd55c.tar.gz
onionshare-f0e0762bce78393b55f596f42d305a0699acd55c.zip
Merge pull request #897 from micahflee/develop
Version 2.0 🔥
-rw-r--r--.circleci/config.yml55
-rw-r--r--.github/CODEOWNERS8
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml22
-rw-r--r--BUILD.md193
-rw-r--r--CHANGELOG.md13
-rw-r--r--LICENSE2
-rw-r--r--MANIFEST.in5
-rw-r--r--README.md34
-rwxr-xr-xdev_scripts/onionshare2
-rwxr-xr-xdev_scripts/onionshare-gui2
-rw-r--r--install/Info.plist18
-rwxr-xr-xinstall/build_osx.sh5
-rwxr-xr-xinstall/build_rpm.sh2
-rw-r--r--install/check_lacked_trans.py15
-rw-r--r--install/get-tor-osx.py12
-rw-r--r--install/get-tor-windows.py8
-rw-r--r--install/licenses/license-jquery.txt20
-rw-r--r--install/licenses/license-onionshare.txt3
-rw-r--r--install/licenses/readme.txt2
-rw-r--r--install/macos_sandbox/child.plist10
-rw-r--r--install/macos_sandbox/parent.plist39
-rw-r--r--install/onionshare.appdata.xml35
-rw-r--r--install/onionshare.desktop7
-rw-r--r--install/onionshare.nsi400
-rw-r--r--install/pyinstaller.spec8
-rw-r--r--install/requirements-tests.txt10
-rw-r--r--install/requirements-windows.txt14
-rw-r--r--install/requirements.txt33
-rwxr-xr-xinstall/scripts/onionshare2
-rwxr-xr-xinstall/scripts/onionshare-gui2
-rw-r--r--install/scripts/onionshare-pyinstaller2
-rw-r--r--onionshare/__init__.py182
-rw-r--r--onionshare/common.py666
-rw-r--r--onionshare/onion.py171
-rw-r--r--onionshare/onionshare.py60
-rw-r--r--onionshare/settings.py110
-rw-r--r--onionshare/socks.py530
-rw-r--r--onionshare/strings.py49
-rw-r--r--onionshare/web.py454
-rw-r--r--onionshare/web/__init__.py (renamed from onionshare_gui/alert.py)22
-rw-r--r--onionshare/web/receive_mode.py442
-rw-r--r--onionshare/web/share_mode.py376
-rw-r--r--onionshare/web/web.py276
-rw-r--r--onionshare_gui/__init__.py61
-rw-r--r--onionshare_gui/downloads.py134
-rw-r--r--onionshare_gui/mode/__init__.py337
-rw-r--r--onionshare_gui/mode/history.py607
-rw-r--r--onionshare_gui/mode/receive_mode/__init__.py217
-rw-r--r--onionshare_gui/mode/share_mode/__init__.py382
-rw-r--r--onionshare_gui/mode/share_mode/file_selection.py (renamed from onionshare_gui/file_selection.py)138
-rw-r--r--onionshare_gui/mode/share_mode/threads.py63
-rw-r--r--onionshare_gui/onionshare_gui.py882
-rw-r--r--onionshare_gui/server_status.py188
-rw-r--r--onionshare_gui/settings_dialog.py650
-rw-r--r--onionshare_gui/threads.py77
-rw-r--r--onionshare_gui/tor_connection_dialog.py53
-rw-r--r--onionshare_gui/update_checker.py60
-rw-r--r--onionshare_gui/widgets.py69
-rw-r--r--screenshots/appdata-client.pngbin57679 -> 0 bytes
-rw-r--r--screenshots/appdata-onionshare-receive-client.pngbin0 -> 361337 bytes
-rw-r--r--screenshots/appdata-onionshare-receive-server.pngbin0 -> 340924 bytes
-rw-r--r--screenshots/appdata-onionshare-share-client.pngbin0 -> 372511 bytes
-rw-r--r--screenshots/appdata-onionshare-share-server.pngbin0 -> 526914 bytes
-rw-r--r--screenshots/appdata-server.pngbin119402 -> 0 bytes
-rw-r--r--screenshots/client.pngbin40602 -> 0 bytes
-rw-r--r--screenshots/onionshare-receive-client.pngbin0 -> 67444 bytes
-rw-r--r--screenshots/onionshare-receive-server.pngbin0 -> 78558 bytes
-rw-r--r--screenshots/onionshare-share-client.pngbin0 -> 43404 bytes
-rw-r--r--screenshots/onionshare-share-server.pngbin0 -> 57758 bytes
-rw-r--r--screenshots/server.pngbin45923 -> 0 bytes
-rw-r--r--setup.py55
-rw-r--r--share/html/404.html16
-rw-r--r--share/html/denied.html19
-rw-r--r--share/html/index.html208
-rw-r--r--share/images/open_folder.pngbin0 -> 221 bytes
-rw-r--r--share/images/receive_icon_toggle.pngbin0 -> 380 bytes
-rw-r--r--share/images/receive_icon_toggle_selected.pngbin0 -> 468 bytes
-rw-r--r--share/images/receive_icon_transparent.pngbin0 -> 2138 bytes
-rw-r--r--share/images/settings.pngbin411 -> 443 bytes
-rw-r--r--share/images/share_completed.png (renamed from share/images/download_completed.png)bin646 -> 646 bytes
-rw-r--r--share/images/share_completed_none.png (renamed from share/images/download_completed_none.png)bin437 -> 437 bytes
-rw-r--r--share/images/share_icon_toggle.pngbin0 -> 389 bytes
-rw-r--r--share/images/share_icon_toggle_selected.pngbin0 -> 473 bytes
-rw-r--r--share/images/share_icon_transparent.pngbin0 -> 2096 bytes
-rw-r--r--share/images/share_in_progress.png (renamed from share/images/download_in_progress.png)bin638 -> 638 bytes
-rw-r--r--share/images/share_in_progress_none.png (renamed from share/images/download_in_progress_none.png)bin412 -> 412 bytes
-rw-r--r--share/locale/am.json185
-rw-r--r--share/locale/ar.json186
-rw-r--r--share/locale/bg.json185
-rw-r--r--share/locale/bn.json204
-rw-r--r--share/locale/ca.json217
-rw-r--r--share/locale/cs.json68
-rw-r--r--share/locale/da.json280
-rw-r--r--share/locale/de.json219
-rw-r--r--share/locale/el.json217
-rw-r--r--share/locale/en.json250
-rw-r--r--share/locale/eo.json45
-rw-r--r--share/locale/es.json239
-rw-r--r--share/locale/fa.json216
-rw-r--r--share/locale/fi.json26
-rw-r--r--share/locale/fr.json242
-rw-r--r--share/locale/ga.json185
-rw-r--r--share/locale/gu.json185
-rw-r--r--share/locale/he.json188
-rw-r--r--share/locale/hu.json185
-rw-r--r--share/locale/id.json185
-rw-r--r--share/locale/is.json185
-rw-r--r--share/locale/it.json243
-rw-r--r--share/locale/ja.json216
-rw-r--r--share/locale/ka.json185
-rw-r--r--share/locale/ko.json185
-rw-r--r--share/locale/lg.json185
-rw-r--r--share/locale/mk.json185
-rw-r--r--share/locale/nl.json238
-rw-r--r--share/locale/no.json224
-rw-r--r--share/locale/pa.json185
-rw-r--r--share/locale/pl.json188
-rw-r--r--share/locale/pt.json10
-rw-r--r--share/locale/pt_BR.json212
-rw-r--r--share/locale/pt_PT.json185
-rw-r--r--share/locale/ro.json185
-rw-r--r--share/locale/ru.json223
-rw-r--r--share/locale/sl.json185
-rw-r--r--share/locale/sn.json188
-rw-r--r--share/locale/sv.json215
-rw-r--r--share/locale/tr.json35
-rw-r--r--share/locale/wo.json185
-rw-r--r--share/locale/yo.json185
-rw-r--r--share/locale/zh_Hans.json216
-rw-r--r--share/locale/zh_Hant.json185
-rw-r--r--share/static/css/style.css243
-rw-r--r--share/static/img/ajax.gifbin0 -> 847 bytes
-rw-r--r--share/static/img/favicon.ico (renamed from share/images/favicon.ico)bin4286 -> 4286 bytes
-rw-r--r--share/static/img/logo.pngbin0 -> 3824 bytes
-rw-r--r--share/static/img/logo_large.pngbin0 -> 9663 bytes
-rw-r--r--share/static/img/web_file.png (renamed from share/images/web_file.png)bin251 -> 251 bytes
-rw-r--r--share/static/img/web_folder.png (renamed from share/images/web_folder.png)bin338 -> 338 bytes
-rw-r--r--share/static/js/jquery-3.3.1.min.js2
-rw-r--r--share/static/js/receive-noscript.js2
-rw-r--r--share/static/js/receive.js127
-rw-r--r--share/static/js/send.js75
-rw-r--r--share/templates/403.html16
-rw-r--r--share/templates/404.html16
-rw-r--r--share/templates/denied.html10
-rw-r--r--share/templates/receive.html58
-rw-r--r--share/templates/receive_noscript_xss.html35
-rw-r--r--share/templates/send.html56
-rw-r--r--share/templates/thankyou.html22
-rw-r--r--share/torrc_template1
-rw-r--r--share/torrc_template-windows9
-rw-r--r--share/version.txt2
-rw-r--r--stdeb.cfg8
-rw-r--r--tests/GuiBaseTest.py321
-rw-r--r--tests/GuiReceiveTest.py147
-rw-r--r--tests/GuiShareTest.py204
-rw-r--r--tests/SettingsGuiBaseTest.py241
-rw-r--r--tests/TorGuiBaseTest.py173
-rw-r--r--tests/TorGuiReceiveTest.py59
-rw-r--r--tests/TorGuiShareTest.py95
-rw-r--r--tests/__init__.py (renamed from test/__init__.py)0
-rw-r--r--tests/conftest.py (renamed from test/conftest.py)55
-rw-r--r--tests/local_onionshare_404_public_mode_skips_ratelimit_test.py27
-rw-r--r--tests/local_onionshare_404_triggers_ratelimit_test.py26
-rw-r--r--tests/local_onionshare_quitting_during_share_prompts_warning_test.py33
-rw-r--r--tests/local_onionshare_receive_mode_sender_closed_test.py26
-rw-r--r--tests/local_onionshare_receive_mode_timer_test.py26
-rw-r--r--tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py25
-rw-r--r--tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py26
-rw-r--r--tests/local_onionshare_receive_mode_upload_public_mode_test.py26
-rw-r--r--tests/local_onionshare_receive_mode_upload_test.py25
-rw-r--r--tests/local_onionshare_settings_dialog_legacy_tor_test.py26
-rw-r--r--tests/local_onionshare_settings_dialog_no_tor_test.py26
-rw-r--r--tests/local_onionshare_settings_dialog_v3_tor_test.py26
-rw-r--r--tests/local_onionshare_share_mode_download_public_mode_test.py25
-rw-r--r--tests/local_onionshare_share_mode_download_stay_open_test.py25
-rw-r--r--tests/local_onionshare_share_mode_download_test.py24
-rw-r--r--tests/local_onionshare_share_mode_large_download_test.py24
-rw-r--r--tests/local_onionshare_share_mode_slug_persistent_test.py28
-rw-r--r--tests/local_onionshare_share_mode_timer_test.py26
-rw-r--r--tests/local_onionshare_share_mode_timer_too_short_test.py33
-rw-r--r--tests/local_onionshare_share_mode_unreadable_file_test.py24
-rw-r--r--tests/onionshare_790_cancel_on_second_share_test.py30
-rw-r--r--tests/onionshare_receive_mode_upload_public_mode_test.py27
-rw-r--r--tests/onionshare_receive_mode_upload_test.py26
-rw-r--r--tests/onionshare_share_mode_cancel_share_test.py26
-rw-r--r--tests/onionshare_share_mode_download_public_mode_test.py26
-rw-r--r--tests/onionshare_share_mode_download_stay_open_test.py26
-rw-r--r--tests/onionshare_share_mode_download_test.py25
-rw-r--r--tests/onionshare_share_mode_persistent_test.py30
-rw-r--r--tests/onionshare_share_mode_stealth_test.py30
-rw-r--r--tests/onionshare_share_mode_timer_test.py27
-rw-r--r--tests/onionshare_share_mode_tor_connection_killed_test.py26
-rw-r--r--tests/onionshare_share_mode_v2_onion_test.py27
-rw-r--r--tests/test_helpers.py (renamed from test/test_helpers.py)2
-rw-r--r--tests/test_onionshare.py (renamed from test/test_onionshare.py)7
-rw-r--r--tests/test_onionshare_common.py (renamed from test/test_onionshare_common.py)163
-rw-r--r--tests/test_onionshare_settings.py (renamed from test/test_onionshare_settings.py)40
-rw-r--r--tests/test_onionshare_strings.py (renamed from test/test_onionshare_strings.py)41
-rw-r--r--tests/test_onionshare_web.py226
200 files changed, 15854 insertions, 4128 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..accbc808
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,55 @@
+# Python CircleCI 2.0 configuration file
+#
+# Check https://circleci.com/docs/2.0/language-python/ for more details
+#
+version: 2
+workflows:
+ version: 2
+ test:
+ jobs:
+ - test-3.5
+ - test-3.6
+ - test-3.7
+
+jobs:
+ test-3.5: &test-template
+ docker:
+ - image: circleci/python:3.5.6
+
+ working_directory: ~/repo
+
+ steps:
+ - checkout
+
+ - run:
+ name: install dependencies
+ command: |
+ sudo apt-get update
+ sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy
+ sudo pip3 install -r install/requirements.txt
+ sudo pip3 install -r install/requirements-tests.txt
+ sudo pip3 install pytest-cov flake8
+
+ # run tests!
+ - run:
+ name: run flake tests
+ command: |
+ # stop the build if there are Python syntax errors or undefined names
+ flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+
+ - run:
+ name: run tests
+ command: |
+ xvfb-run pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/
+
+ test-3.6:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.6.6
+
+ test-3.7:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.7.1
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1cd5a1f6..42d1840f 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1,9 @@
* @micahflee
+
+# localization
+/share/locale/ @emmapeel2
+
+# tests
+/tests/ @mig5
+/tests_gui_local/ @mig5
+/tests_gui_tor/ @mig5
diff --git a/.gitignore b/.gitignore
index b202993b..12201adb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+__pycache__
*.py[cod]
# C extensions
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9010e77a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-language: python
-# sudo: required
-dist: trusty
-python:
- - "3.4"
- - "3.5"
- - "3.6"
- - "3.6-dev"
- - "3.7-dev"
- - "nightly"
-# command to install dependencies
-install:
- - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8
-before_script:
- # stop the build if there are Python syntax errors or undefined names
- - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-# command to run tests
-script: pytest --cov=onionshare test/
-after_success:
- - coveralls
diff --git a/BUILD.md b/BUILD.md
index 77d5ee0e..3d664ac8 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -11,9 +11,17 @@ cd onionshare
Install the needed dependencies:
-For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy`
+For Debian-like distros:
-For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4`
+```
+apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-distutils python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python
+```
+
+For Fedora-like distros:
+
+```
+dnf install -y python3-flask python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build
+```
After that you can try both the CLI and the GUI version of OnionShare:
@@ -28,6 +36,8 @@ Create a .deb on Debian-like distros: `./install/build_deb.sh`
Create a .rpm on Fedora-like distros: `./install/build_rpm.sh`
+For OpenSuSE: There are instructions for building [in the wiki](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support#opensuse-leap-150).
+
For ArchLinux: There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare.
If you find that these instructions don't work for your Linux distribution or version, consult the [Linux Distribution Support wiki guide](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support), which might contain extra instructions.
@@ -36,26 +46,68 @@ If you find that these instructions don't work for your Linux distribution or ve
Install Xcode from the Mac App Store. Once it's installed, run it for the first time to set it up. Also, run this to make sure command line tools are installed: `xcode-select --install`. And finally, open Xcode, go to Preferences > Locations, and make sure under Command Line Tools you select an installed version from the dropdown. (This is required for installing Qt5.)
-Download and install Python 3.6.4 from https://www.python.org/downloads/release/python-364/. I downloaded `python-3.6.4-macosx10.6.pkg`.
+Download and install Python 3.7.2 from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2-macosx10.9.pkg`.
-You may also need to run the command `/Applications/Python\ 3.6/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error.
+You may also need to run the command `/Applications/Python\ 3.7/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error.
-Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.2-online.dmg`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.10.0 -- all you need is to check `Qt > Qt 5.10.0 > macOS`.
+Install Qt 5.11.3 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.6-online.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.11.3` > `macOS`.
Now install some python dependencies with pip (note, there's issues building a .app if you install this in a virtualenv):
```sh
-sudo pip3 install -r install/requirements.txt
+pip3 install -r install/requirements.txt
```
-You can run both the CLI and GUI versions of OnionShare without building an bundle:
+#### You can run both the CLI and GUI versions of OnionShare without building an bundle
```sh
./dev_scripts/onionshare
./dev_scripts/onionshare-gui
```
-To build the app bundle:
+#### Building PyInstaller
+
+If you want to build an app bundle, you'll need to use PyInstaller. Recently there has been issues with installing PyInstaller using pip, so here's how to build it from source. First, make sure you don't have PyInstaller currently installed:
+
+```sh
+pip3 uninstall PyInstaller
+```
+
+Change to a folder where you keep source code, and clone the PyInstaller git repo:
+
+```sh
+git clone https://github.com/pyinstaller/pyinstaller.git
+```
+
+Verify the v3.4 git tag:
+
+```sh
+cd pyinstaller
+gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65
+git tag -v v3.4
+```
+
+It should say `Good signature from "Hartmut Goebel <h.goebel@goebel-consult.de>`. If it verified successfully, checkout the tag:
+
+```sh
+git checkout v3.4
+```
+
+And compile the bootloader, following [these instructions](https://pyinstaller.readthedocs.io/en/stable/bootloader-building.html#building-for-mac-os-x). To compile, run this:
+
+```sh
+cd bootloader
+python3 waf distclean all --target-arch=64bit
+```
+
+Finally, install the PyInstaller module into your local site-packages:
+
+```sh
+cd ..
+python3 setup.py install
+```
+
+#### To build the app bundle
```sh
install/build_osx.sh
@@ -63,7 +115,7 @@ install/build_osx.sh
Now you should have `dist/OnionShare.app`.
-To codesign and build a pkg for distribution:
+#### To codesign and build a pkg for distribution
```sh
install/build_osx.sh --release
@@ -75,17 +127,15 @@ Now you should have `dist/OnionShare.pkg`.
### Setting up your dev environment
-Download Python 3.6.4, 32-bit (x86) from https://www.python.org/downloads/release/python-364/. I downloaded `python-3.6.4.exe`. When installing it, make sure to check the "Add Python 3.6 to PATH" checkbox on the first page of the installer.
+Download Python 3.7.2, 32-bit (x86) from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer.
Open a command prompt, cd to the onionshare folder, and install dependencies with pip:
```cmd
-pip3 install -r install\requirements-windows.txt
+pip install -r install\requirements.txt
```
-Download and install pywin32 (build 221, x86, for python 3.6) from https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/. I downloaded `pywin32-221.win32-py3.6.exe`.
-
-Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.4-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.11.0. You only need to install the `MSVC 2015 32-bit` component, as well as all of the the `Qt` components, for that that version.
+Install the Qt 5.11.3 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.6-online.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.11.3` > `MSVC 2015 32-bit`.
After that you can try both the CLI and the GUI version of OnionShare:
@@ -94,7 +144,7 @@ python dev_scripts\onionshare
python dev_scripts\onionshare-gui
```
-If you want to build a .exe:
+#### If you want to build a .exe
These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH.
@@ -106,17 +156,88 @@ Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-
Add the following directories to the path:
-* `C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86`
-* `C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86`
-* `C:\Users\user\AppData\Local\Programs\Python\Python36-32\Lib\site-packages\PyQt5\Qt\bin`
+* `C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86`
+* `C:\Program Files (x86)\Windows Kits\10\Redist\10.0.17763.0\ucrt\DLLs\x86`
+* `C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyQt5\Qt\bin`
* `C:\Program Files (x86)\7-Zip`
-If you want to build the installer:
+#### If you want the .exe to not get falsely flagged as malicious by anti-virus software
+
+OnionShare uses PyInstaller to turn the python source code into Windows executable `.exe` file. Apparently, malware developers also use PyInstaller, and some anti-virus vendors have included snippets of PyInstaller code in their virus definitions. To avoid this, you have to compile the Windows PyInstaller bootloader yourself instead of using the pre-compiled one that comes with PyInstaller.
+
+(If you don't care about this, you can install PyInstaller with `pip install PyInstaller==3.4`.)
+
+Here's how to compile the PyInstaller bootloader:
+
+Download and install [Microsoft Build Tools for Visual Studio 2017](https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017). I downloaded `vs_buildtools.exe`. In the installer, check the box next to "Visual C++ build tools". Click "Individual components", and under "Compilers, build tools and runtimes", check "Windows Universal CRT SDK". Then click install. When installation is done, you may have to reboot your computer.
+
+Then, enable the 32-bit Visual C++ Toolset on the Command Line like this:
+
+```
+cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build"
+vcvars32.bat
+```
+
+Make sure you have a new enough `setuptools`:
+
+```
+pip install setuptools==40.6.3
+```
+
+Now make sure you don't have PyInstaller installed from pip:
-* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.03-setup.exe`.
+```
+pip uninstall PyInstaller
+rmdir C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyInstaller /S
+```
+
+Change to a folder where you keep source code, and clone the PyInstaller git repo:
+
+```
+git clone https://github.com/pyinstaller/pyinstaller.git
+```
+
+To verify the git tag, you first need the signing key's PGP key, which means you need `gpg`. If you installed git from git-scm.com, you can run this from Git Bash:
+
+```
+gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65
+```
+
+And now verify the tag:
+
+```
+cd pyinstaller
+git tag -v v3.4
+```
+
+It should say `Good signature from "Hartmut Goebel <h.goebel@goebel-consult.de>`. If it verified successfully, checkout the tag:
+
+```
+git checkout v3.4
+```
+
+And compile the bootloader, following [these instructions](https://pythonhosted.org/PyInstaller/bootloader-building.html). To compile, run this:
+
+```
+cd bootloader
+python waf distclean all --target-arch=32bit --msvc_targets=x86
+```
+
+Finally, install the PyInstaller module into your local site-packages:
+
+```
+cd ..
+python setup.py install
+```
+
+Now the next time you use PyInstaller to build OnionShare, the `.exe` file should not be flagged as malicious by anti-virus.
+
+#### If you want to build the installer
+
+* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.04-setup.exe`.
* Add `C:\Program Files (x86)\NSIS` to the path.
-If you want to sign binaries with Authenticode:
+#### If you want to sign binaries with Authenticode
* You'll need a code signing certificate. I got an open source code signing certificate from [Certum](https://www.certum.eu/certum/cert,offer_en_open_source_cs.xml).
* Once you get a code signing key and certificate and covert it to a pfx file, import it into your certificate store.
@@ -135,8 +256,34 @@ This will prompt you to codesign three binaries and execute one unsigned binary.
## Tests
-OnionShare includes PyTest unit tests. To run the tests:
+OnionShare includes PyTest unit tests. To run the tests, first install some dependencies:
+
+```sh
+pip3 install -r install/requirements-tests.txt
+```
+
+Then you can run `pytest` against the `tests/` directory.
+
+```sh
+pytest tests/
+```
+
+You can run GUI tests like this:
+
+```sh
+pytest --rungui tests/
+```
+
+If you would like to also run the GUI unit tests in 'tor' mode, start Tor Browser in the background, then run:
+
+```sh
+pytest --rungui --runtor tests/
+```
+
+Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive.
+
+You can also choose to wrap the tests in `xvfb-run` so that a ton of OnionShare windows don't pop up on your desktop (you may need to install the `xorg-x11-server-Xvfb` package), like this:
```sh
-pytest test/
+xvfb-run pytest --rungui tests/
```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 547ba611..392df41f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# OnionShare Changelog
+## 2.0
+
+* New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files
+* New feature: Support for next generation onion services
+* New feature: macOS sandbox is enabled
+* New feature: Public mode feature, for public uses of OnionShare, which when enabled turns off slugs in the URL and removes the limit on how many 404 requests can be made
+* New feature: If you're sharing a single file, don't zip it up
+* New feature: Full support for meek_lite (Azure) bridges
+* New feature: Allow selecting your language from a dropdown
+* New translations: Bengali (বাংলা), Catalan (Català), Danish (Dansk), French (Français), Greek (Ελληνικά), Italian (Italiano), Japanese (日本語), Persian (فارسی), Portuguese Brazil (Português Brasil), Russian (Русский), Spanish (Español), Swedish (Svenska)
+* Several bugfixes
+* Invisible to users, but this version includes some major refactoring of the codebase, and a robust set of unit tests which makes OnionShare easier to maintain going forward
+
## 1.3.2
* Bug fix: In debug mode, stop saving flask debug log in /tmp, where all users can access it
diff --git a/LICENSE b/LICENSE
index 5a03b106..668a10f0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
(Note: Third-party licenses can be found under install/licenses/.)
OnionShare
-Copyright © 2018 Micah Lee <micah@micahflee.com>
+Copyright © 2014-2018 Micah Lee <micah@micahflee.com>
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
diff --git a/MANIFEST.in b/MANIFEST.in
index f4d1c078..71af3740 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,9 +4,10 @@ include BUILD.md
include share/*
include share/images/*
include share/locale/*
-include share/html/*
+include share/templates/*
+include share/static/*
include install/onionshare.desktop
include install/onionshare.appdata.xml
include install/onionshare80.xpm
include install/scripts/onionshare-nautilus.py
-include test/*.py
+include tests/*.py
diff --git a/README.md b/README.md
index 55487045..6b6ed836 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,38 @@
# OnionShare
-[![Build Status](https://travis-ci.org/micahflee/onionshare.png)](https://travis-ci.org/micahflee/onionshare)
+[OnionShare](https://onionshare.org) is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in [Tor Browser](https://www.torproject.org/) to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account.
-[OnionShare](https://onionshare.org) lets you securely and anonymously share files of any size. It works by starting a web server, making it accessible as a Tor Onion Service, and generating an unguessable URL to access and download the files. It does _not_ require setting up a separate server or using a third party file-sharing service. You host the files on your own computer and use a Tor Onion Service to make it temporarily accessible over the internet. The receiving user just needs to open the URL in Tor Browser to download the file.
+Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing with can access the files.
## Documentation
To learn how OnionShare works, what its security properties are, and how to use it, check out the [wiki](https://github.com/micahflee/onionshare/wiki).
-## Downloading Onionshare
+## Downloading OnionShare
-You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). It should be available in your package manager for Linux, and it's included by default in [Tails](https://tails.boum.org).
+You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org).
-## Developing OnionShare
+For Ubuntu-like Linux distributions, you could use this PPA to get the latest version:
-You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our developers mailing list [here](https://lists.riseup.net/www/info/onionshare-dev).
+```
+sudo add-apt-repository ppa:micahflee/ppa
+sudo apt install -y onionshare
+```
-# Screenshots
+OnionShare may also be available in your Linux distribution's package manager. Check [this wiki page](https://github.com/micahflee/onionshare/wiki/How-Do-I-Install-Onionshare) for more information.
-![Server Screenshot](/screenshots/server.png)
-![Client Screenshot](/screenshots/client.png)
+## Contributing to OnionShare
+
+You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our mailing list [here](https://lists.riseup.net/www/info/onionshare-dev), and join our public Keybase team [here](https://keybase.io/team/onionshare).
+
+Test status: [![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare)
+
+# Screenshots
+
+![Share mode OnionShare](/screenshots/onionshare-share-server.png)
+
+![Share mode Tor Browser](/screenshots/onionshare-share-client.png)
+
+![Receive mode OnionShare](/screenshots/onionshare-receive-server.png)
+
+![Receive mode Tor Browser](/screenshots/onionshare-receive-client.png)
diff --git a/dev_scripts/onionshare b/dev_scripts/onionshare
index 358293e1..29cdedcc 100755
--- a/dev_scripts/onionshare
+++ b/dev_scripts/onionshare
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/dev_scripts/onionshare-gui b/dev_scripts/onionshare-gui
index 4e008809..40ab742c 100755
--- a/dev_scripts/onionshare-gui
+++ b/dev_scripts/onionshare-gui
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/install/Info.plist b/install/Info.plist
deleted file mode 100644
index 0125c1d5..00000000
--- a/install/Info.plist
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>CFBundleDevelopmentRegion</key>
- <string>English</string>
- <key>CFBundleExecutable</key>
- <string>onionshare-gui</string>
- <key>CFBundleIdentifier</key>
- <string>com.micahflee.onionshare</string>
- <key>NSHighResolutionCapable</key>
- <true/>
- <key>CFBundleShortVersionString</key>
- <string>{VERSION}</string>
- <key>CFBundleIconFile</key>
- <string>icon.icns</string>
-</dict>
-</plist>
diff --git a/install/build_osx.sh b/install/build_osx.sh
index f6b27d9b..010e3edb 100755
--- a/install/build_osx.sh
+++ b/install/build_osx.sh
@@ -23,9 +23,12 @@ if [ "$1" = "--release" ]; then
PKG_PATH="$ROOT/dist/OnionShare.pkg"
IDENTITY_NAME_APPLICATION="Developer ID Application: Micah Lee"
IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee"
+ ENTITLEMENTS_CHILD_PATH="$ROOT/install/macos_sandbox/child.plist"
+ ENTITLEMENTS_PARENT_PATH="$ROOT/install/macos_sandbox/parent.plist"
echo "Codesigning the app bundle"
- codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH"
+ codesign --deep -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_CHILD_PATH" "$APP_PATH"
+ codesign -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_PARENT_PATH" "$APP_PATH"
echo "Creating an installer"
productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH"
diff --git a/install/build_rpm.sh b/install/build_rpm.sh
index c103262c..7a34b271 100755
--- a/install/build_rpm.sh
+++ b/install/build_rpm.sh
@@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
rm -r build dist >/dev/null 2>&1
# build binary package
-python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, nautilus-python, tor, obfs4"
+python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
# install it
echo ""
diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py
index 3313db7c..1caa6b27 100644
--- a/install/check_lacked_trans.py
+++ b/install/check_lacked_trans.py
@@ -54,7 +54,12 @@ def main():
dir = args.onionshare_dir
- src = files_in(dir, 'onionshare') + files_in(dir, 'onionshare_gui')
+ src = files_in(dir, 'onionshare') + \
+ files_in(dir, 'onionshare_gui') + \
+ files_in(dir, 'onionshare_gui/share_mode') + \
+ files_in(dir, 'onionshare_gui/receive_mode') + \
+ files_in(dir, 'install/scripts') + \
+ files_in(dir, 'tests')
pysrc = [p for p in src if p.endswith('.py')]
lang_code = args.lang_code
@@ -64,11 +69,11 @@ def main():
for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded('utf-8')):
# search `strings._('translate_key')`
# `strings._('translate_key', True)`
- m = re.search(r'strings\._\((.*?)\)', line)
+ m = re.findall(r'strings\._\((.*?)\)', line)
if m:
- arg = m.group(1)
- key = arg.split(',')[0].strip('''"' ''')
- translate_keys.add(key)
+ for match in m:
+ key = match.split(',')[0].strip('''"' ''')
+ translate_keys.add(key)
if args.show_all_keys:
for k in sorted(translate_keys):
diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py
index 3c498dfe..d966173c 100644
--- a/install/get-tor-osx.py
+++ b/install/get-tor-osx.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,14 +35,14 @@ import subprocess
import requests
def main():
- dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/TorBrowser-7.5.5-osx64_en-US.dmg'
- dmg_filename = 'TorBrowser-7.5.5-osx64_en-US.dmg'
- expected_dmg_sha256 = '2b445e4237cdd9be0e71e65f76db5d36f0d6c37532982d642803b57e388e4636'
+ dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.5/TorBrowser-8.0.5-osx64_en-US.dmg'
+ dmg_filename = 'TorBrowser-8.0.5-osx64_en-US.dmg'
+ expected_dmg_sha256 = '08f0f79181319b74f8ad3a3f8c72a46356ec47f1ca3e22eb42d92e51451d9411'
# Build paths
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
working_path = os.path.join(root_path, 'build', 'tor')
- dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'TorBrowser.app', 'Contents')
+ dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'Tor Browser.app', 'Contents')
dmg_path = os.path.join(working_path, dmg_filename)
dist_path = os.path.join(root_path, 'dist', 'OnionShare.app', 'Contents')
@@ -88,7 +88,7 @@ def main():
shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip6'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip6'))
os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 0o755)
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'))
- shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'))
+ shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'))
os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755)
# obfs4proxy binary
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'))
diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py
index 44c4ac23..aad45e94 100644
--- a/install/get-tor-windows.py
+++ b/install/get-tor-windows.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,9 +33,9 @@ import subprocess
import requests
def main():
- exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/torbrowser-install-7.5.5_en-US.exe'
- exe_filename = 'torbrowser-install-7.5.5_en-US.exe'
- expected_exe_sha256 = '992f9a6658001c3419ed3695a908eef4fb7feb1cd549389bdacbadb7f8cb08a7'
+ exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.5/torbrowser-install-8.0.5_en-US.exe'
+ exe_filename = 'torbrowser-install-8.0.5_en-US.exe'
+ expected_exe_sha256 = '860fdd06e4ea8dd4c46f221676251f5cc528676d4e256559ee3831a5f97492f1'
# Build paths
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
diff --git a/install/licenses/license-jquery.txt b/install/licenses/license-jquery.txt
new file mode 100644
index 00000000..e3dbacb9
--- /dev/null
+++ b/install/licenses/license-jquery.txt
@@ -0,0 +1,20 @@
+Copyright JS Foundation and other contributors, https://js.foundation/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/install/licenses/license-onionshare.txt b/install/licenses/license-onionshare.txt
index dc049cff..77d05583 100644
--- a/install/licenses/license-onionshare.txt
+++ b/install/licenses/license-onionshare.txt
@@ -1,5 +1,4 @@
-OnionShare
-Copyright © 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
diff --git a/install/licenses/readme.txt b/install/licenses/readme.txt
index c92e516d..3dd5d2fe 100644
--- a/install/licenses/readme.txt
+++ b/install/licenses/readme.txt
@@ -1 +1 @@
-This folder contains the software licenses for 3rd-party binaries included with OnionShare.
+This folder contains 3rd-party licenses for software included with OnionShare.
diff --git a/install/macos_sandbox/child.plist b/install/macos_sandbox/child.plist
new file mode 100644
index 00000000..06d88f66
--- /dev/null
+++ b/install/macos_sandbox/child.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.inherit</key>
+ <true/>
+</dict>
+</plist>
diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist
new file mode 100644
index 00000000..3929abe9
--- /dev/null
+++ b/install/macos_sandbox/parent.plist
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <!-- Enable app sandbox -->
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+
+ <!-- Both OnionShare and Tor need network server and client -->
+ <key>com.apple.security.network.server</key>
+ <true/>
+ <key>com.apple.security.network.client</key>
+ <true/>
+
+ <!-- In share mode, users need to be able to select files, and in receive mode,
+ users need to be able to choose a folder to save files to -->
+ <key>com.apple.security.files.user-selected.read-write</key>
+ <true/>
+
+ <!-- Flask needs to read this mime.types file when starting an HTTP server -->
+ <key>com.apple.security.temporary-exception.files.absolute-path.read-only</key>
+ <array>
+ <string>/private/etc/apache2/mime.types</string>
+ </array>
+
+ <!-- For OnionShare to be able to connect to Tor Browser's tor control port,
+ it needs to read it's control_auth_cookie file -->
+ <key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
+ <array>
+ <string>/Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie</string>
+ </array>
+
+ <!-- In receive mode, OnionShare needs to be able to write to ~/OnionShare -->
+ <key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
+ <array>
+ <string>/OnionShare/</string>
+ </array>
+</dict>
+</plist>
diff --git a/install/onionshare.appdata.xml b/install/onionshare.appdata.xml
index fd78616e..2302a2e8 100644
--- a/install/onionshare.appdata.xml
+++ b/install/onionshare.appdata.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright 2016 Micah Lee <micah@micahflee.com> -->
+<!-- Copyright 2018 Micah Lee <micah@micahflee.com> -->
<component type="desktop">
<id>onionshare.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
@@ -8,25 +8,36 @@
<summary>Securely and anonymously share a file of any size</summary>
<description>
<p>
- OnionShare lets you securely and anonymously share a file of any size with someone. It works
- by starting a web server, making it accessible as a Tor hidden service, and generating an
- unguessable URL to access and download the file.
+ OnionShare lets you securely and anonymously send and receive files. It works by starting a
+ web server, making it accessible as a Tor onion service, and generating an unguessable web
+ address so others can download files from you, or upload files to you. It does <em>not</em>
+ require setting up a separate server or using a third party file-sharing service.
</p>
<p>
- It doesn't require setting up a server on the internet somewhere or using a third party
- filesharing service. You host the file on your own computer and use a Tor hidden service to
- make it temporarily accessible over the internet. The other user just needs to use Tor Browser
- to download the file from you.
+ If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor
+ onion service to make them temporarily accessible over the internet. The receiving user just
+ needs to open the web address in Tor Browser to download the files. If you want to receive files,
+ OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to
+ make it temporarily accessible over the internet. Other users can upload files to you from by
+ loading the web address in Tor Browser.
</p>
</description>
<screenshots>
<screenshot type="default">
- <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-server.png</image>
- <caption>Sender sharing files with OnionShare</caption>
+ <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-server.png</image>
+ <caption>Sharing files with OnionShare</caption>
</screenshot>
<screenshot>
- <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-client.png</image>
- <caption>Receiver downloading files with Tor Browser</caption>
+ <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-client.png</image>
+ <caption>Downloading OnionShare files using Tor Browser</caption>
+ </screenshot>
+ <screenshot>
+ <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-server.png</image>
+ <caption>Receiving files with OnionShare</caption>
+ </screenshot>
+ <screenshot>
+ <image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-client.png</image>
+ <caption>Uploading files to OnionShare user using Tor Browser</caption>
</screenshot>
</screenshots>
<url type="homepage">https://onionshare.org/</url>
diff --git a/install/onionshare.desktop b/install/onionshare.desktop
index fbac3660..a0861cf8 100644
--- a/install/onionshare.desktop
+++ b/install/onionshare.desktop
@@ -1,11 +1,16 @@
[Desktop Entry]
Name=OnionShare
+GenericName=OnionShare Client
Comment=Share a file securely and anonymously over Tor
Comment[da]=Del en fil sikkert og anonymt over Tor
+Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk
Exec=/usr/bin/onionshare-gui
Terminal=false
Type=Application
Icon=/usr/share/pixmaps/onionshare80.xpm
-Categories=Network;
+Categories=Network;FileTransfer;
Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting;
Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting;
+Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting;
+StartupNotify=true
+StartupWMClass=onionshare
diff --git a/install/onionshare.nsi b/install/onionshare.nsi
index 279f4803..3a4c6c2a 100644
--- a/install/onionshare.nsi
+++ b/install/onionshare.nsi
@@ -3,10 +3,10 @@
!define ABOUTURL "https:\\onionshare.org\"
# change these with each release
-!define INSTALLSIZE 66537
-!define VERSIONMAJOR 1
-!define VERSIONMINOR 3
-!define VERSIONSTRING "1.3.1"
+!define INSTALLSIZE 115186
+!define VERSIONMAJOR 2
+!define VERSIONMINOR 0
+!define VERSIONSTRING "2.0"
RequestExecutionLevel admin
@@ -59,202 +59,7 @@ FunctionEnd
Section "install"
SetOutPath "$INSTDIR"
File "onionshare.ico"
- File "${BINPATH}\api-ms-win-core-console-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-datetime-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-debug-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-errorhandling-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-file-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-file-l1-2-0.dll"
- File "${BINPATH}\api-ms-win-core-file-l2-1-0.dll"
- File "${BINPATH}\api-ms-win-core-handle-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-heap-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-interlocked-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-libraryloader-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-localization-l1-2-0.dll"
- File "${BINPATH}\api-ms-win-core-memory-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-namedpipe-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-processenvironment-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-processthreads-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-processthreads-l1-1-1.dll"
- File "${BINPATH}\api-ms-win-core-profile-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-rtlsupport-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-string-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-synch-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-synch-l1-2-0.dll"
- File "${BINPATH}\api-ms-win-core-sysinfo-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-timezone-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-core-util-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-conio-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-convert-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-environment-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-filesystem-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-heap-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-locale-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-math-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-multibyte-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-process-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-runtime-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-stdio-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-string-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-time-l1-1-0.dll"
- File "${BINPATH}\api-ms-win-crt-utility-l1-1-0.dll"
- File "${BINPATH}\base_library.zip"
- File "${BINPATH}\mfc140u.dll"
- File "${BINPATH}\MSVCP140.dll"
- File "${BINPATH}\onionshare-gui.exe"
- File "${BINPATH}\onionshare-gui.exe.manifest"
- File "${BINPATH}\pyexpat.pyd"
- File "${BINPATH}\PyQt5.Qt.pyd"
- File "${BINPATH}\PyQt5.QtCore.pyd"
- File "${BINPATH}\PyQt5.QtGui.pyd"
- File "${BINPATH}\PyQt5.QtPrintSupport.pyd"
- File "${BINPATH}\PyQt5.QtWidgets.pyd"
- File "${BINPATH}\python3.dll"
- File "${BINPATH}\python36.dll"
- File "${BINPATH}\pythoncom36.dll"
- File "${BINPATH}\pywintypes36.dll"
- File "${BINPATH}\Qt5Core.dll"
- File "${BINPATH}\Qt5Gui.dll"
- File "${BINPATH}\Qt5PrintSupport.dll"
- File "${BINPATH}\Qt5Svg.dll"
- File "${BINPATH}\Qt5Widgets.dll"
- File "${BINPATH}\select.pyd"
- File "${BINPATH}\sip.pyd"
- File "${BINPATH}\ucrtbase.dll"
- File "${BINPATH}\unicodedata.pyd"
- File "${BINPATH}\VCRUNTIME140.dll"
- File "${BINPATH}\win32api.pyd"
- File "${BINPATH}\win32com.shell.shell.pyd"
- File "${BINPATH}\win32trace.pyd"
- File "${BINPATH}\win32ui.pyd"
- File "${BINPATH}\win32wnet.pyd"
- File "${BINPATH}\_asyncio.pyd"
- File "${BINPATH}\_bz2.pyd"
- File "${BINPATH}\_ctypes.pyd"
- File "${BINPATH}\_decimal.pyd"
- File "${BINPATH}\_hashlib.pyd"
- File "${BINPATH}\_lzma.pyd"
- File "${BINPATH}\_multiprocessing.pyd"
- File "${BINPATH}\_overlapped.pyd"
- File "${BINPATH}\_socket.pyd"
- File "${BINPATH}\_ssl.pyd"
- File "${BINPATH}\_win32sysloader.pyd"
-
- SetOutPath "$INSTDIR\Include"
- File "${BINPATH}\Include\pyconfig.h"
-
- SetOutPath "$INSTDIR\lib2to3"
- File "${BINPATH}\lib2to3\Grammar.txt"
- File "${BINPATH}\lib2to3\Grammar3.6.2.candidate.2.pickle"
- File "${BINPATH}\lib2to3\Grammar3.6.2.final.0.pickle"
- File "${BINPATH}\lib2to3\Grammar3.6.3.candidate.1.pickle"
- File "${BINPATH}\lib2to3\Grammar3.6.3.final.0.pickle"
- File "${BINPATH}\lib2to3\Grammar3.6.4.candidate.1.pickle"
- File "${BINPATH}\lib2to3\Grammar3.6.4.final.0.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar.txt"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.2.candidate.2.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.2.final.0.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.3.candidate.1.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.3.final.0.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.4.candidate.1.pickle"
- File "${BINPATH}\lib2to3\PatternGrammar3.6.4.final.0.pickle"
-
- SetOutPath "$INSTDIR\lib2to3\tests\data"
- File "${BINPATH}\lib2to3\tests\data\README"
-
- SetOutPath "$INSTDIR\licenses"
- File "${BINPATH}\licenses\license-obfs4.txt"
- File "${BINPATH}\licenses\license-onionshare.txt"
- File "${BINPATH}\licenses\license-tor.txt"
- File "${BINPATH}\licenses\readme.txt"
-
- SetOutPath "$INSTDIR\PyQt5\Qt\bin"
- File "${BINPATH}\PyQt5\Qt\bin\qt.conf"
-
- SetOutPath "$INSTDIR\PyQt5\Qt\plugins\iconengines"
- File "${BINPATH}\PyQt5\Qt\plugins\iconengines\qsvgicon.dll"
-
- SetOutPath "$INSTDIR\PyQt5\Qt\plugins\imageformats"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qgif.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qicns.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qico.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qjpeg.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qsvg.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qtga.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qtiff.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qwbmp.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qwebp.dll"
-
- SetOutPath "$INSTDIR\PyQt5\Qt\plugins\platforms"
- File "${BINPATH}\PyQt5\Qt\plugins\platforms\qminimal.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\platforms\qoffscreen.dll"
- File "${BINPATH}\PyQt5\Qt\plugins\platforms\qwindows.dll"
-
- SetOutPath "$INSTDIR\PyQt5\Qt\plugins\printsupport"
- File "${BINPATH}\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll"
-
- SetOutPath "$INSTDIR\share"
- File "${BINPATH}\share\torrc_template"
- File "${BINPATH}\share\torrc_template-windows"
- File "${BINPATH}\share\torrc_template-obfs4"
- File "${BINPATH}\share\torrc_template-meek_lite_amazon"
- File "${BINPATH}\share\torrc_template-meek_lite_azure"
- File "${BINPATH}\share\version.txt"
- File "${BINPATH}\share\wordlist.txt"
-
- SetOutPath "$INSTDIR\share\html"
- File "${BINPATH}\share\html\404.html"
- File "${BINPATH}\share\html\denied.html"
- File "${BINPATH}\share\html\index.html"
-
- SetOutPath "$INSTDIR\share\images"
- File "${BINPATH}\share\images\download_completed.png"
- File "${BINPATH}\share\images\download_completed_none.png"
- File "${BINPATH}\share\images\download_in_progress.png"
- File "${BINPATH}\share\images\download_in_progress_none.png"
- File "${BINPATH}\share\images\favicon.ico"
- File "${BINPATH}\share\images\file_delete.png"
- File "${BINPATH}\share\images\info.png"
- File "${BINPATH}\share\images\logo.png"
- File "${BINPATH}\share\images\logo_transparent.png"
- File "${BINPATH}\share\images\logo_grayscale.png"
- File "${BINPATH}\share\images\server_started.png"
- File "${BINPATH}\share\images\server_stopped.png"
- File "${BINPATH}\share\images\server_working.png"
- File "${BINPATH}\share\images\settings.png"
- File "${BINPATH}\share\images\web_file.png"
- File "${BINPATH}\share\images\web_folder.png"
-
- SetOutPath "$INSTDIR\share\locale"
- File "${BINPATH}\share\locale\cs.json"
- File "${BINPATH}\share\locale\de.json"
- File "${BINPATH}\share\locale\en.json"
- File "${BINPATH}\share\locale\eo.json"
- File "${BINPATH}\share\locale\es.json"
- File "${BINPATH}\share\locale\fi.json"
- File "${BINPATH}\share\locale\fr.json"
- File "${BINPATH}\share\locale\it.json"
- File "${BINPATH}\share\locale\nl.json"
- File "${BINPATH}\share\locale\no.json"
- File "${BINPATH}\share\locale\pt.json"
- File "${BINPATH}\share\locale\ru.json"
- File "${BINPATH}\share\locale\tr.json"
-
- SetOutPath "$INSTDIR\tor\Data\Tor"
- File "${BINPATH}\tor\Data\Tor\geoip"
- File "${BINPATH}\tor\Data\Tor\geoip6"
-
- SetOutPath "$INSTDIR\tor\Tor"
- File "${BINPATH}\tor\Tor\libeay32.dll"
- File "${BINPATH}\tor\Tor\libevent-2-0-5.dll"
- File "${BINPATH}\tor\Tor\libevent_core-2-0-5.dll"
- File "${BINPATH}\tor\Tor\libevent_extra-2-0-5.dll"
- File "${BINPATH}\tor\Tor\libgcc_s_sjlj-1.dll"
- File "${BINPATH}\tor\Tor\libssp-0.dll"
- File "${BINPATH}\tor\Tor\obfs4proxy.exe"
- File "${BINPATH}\tor\Tor\ssleay32.dll"
- File "${BINPATH}\tor\Tor\tor.exe"
- File "${BINPATH}\tor\Tor\zlib1.dll"
+ File /a /r "${BINPATH}\"
# uninstaller
!ifndef INNER
@@ -298,200 +103,7 @@ FunctionEnd
Delete "$SMPROGRAMS\${APPNAME}.lnk"
# remove files
- Delete "$INSTDIR\api-ms-win-core-console-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-datetime-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-debug-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-errorhandling-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-file-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-file-l1-2-0.dll"
- Delete "$INSTDIR\api-ms-win-core-file-l2-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-handle-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-heap-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-interlocked-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-libraryloader-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-localization-l1-2-0.dll"
- Delete "$INSTDIR\api-ms-win-core-memory-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-namedpipe-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-processenvironment-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-processthreads-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-processthreads-l1-1-1.dll"
- Delete "$INSTDIR\api-ms-win-core-profile-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-rtlsupport-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-string-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-synch-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-synch-l1-2-0.dll"
- Delete "$INSTDIR\api-ms-win-core-sysinfo-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-timezone-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-core-util-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-conio-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-convert-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-environment-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-filesystem-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-heap-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-locale-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-math-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-multibyte-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-process-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-runtime-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-stdio-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-string-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-time-l1-1-0.dll"
- Delete "$INSTDIR\api-ms-win-crt-utility-l1-1-0.dll"
- Delete "$INSTDIR\base_library.zip"
- Delete "$INSTDIR\Include\pyconfig.h"
- Delete "$INSTDIR\lib2to3\Grammar.txt"
- Delete "$INSTDIR\lib2to3\Grammar3.6.2.candidate.2.pickle"
- Delete "$INSTDIR\lib2to3\Grammar3.6.2.final.0.pickle"
- Delete "$INSTDIR\lib2to3\Grammar3.6.3.candidate.1.pickle"
- Delete "$INSTDIR\lib2to3\Grammar3.6.3.final.0.pickle"
- Delete "$INSTDIR\lib2to3\Grammar3.6.4.candidate.1.pickle"
- Delete "$INSTDIR\lib2to3\Grammar3.6.4.final.0.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar.txt"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.2.candidate.2.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.2.final.0.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.3.candidate.1.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.3.final.0.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.4.candidate.1.pickle"
- Delete "$INSTDIR\lib2to3\PatternGrammar3.6.4.final.0.pickle"
- Delete "$INSTDIR\lib2to3\tests"
- Delete "$INSTDIR\lib2to3\tests\data"
- Delete "$INSTDIR\lib2to3\tests\data\README"
- Delete "$INSTDIR\licenses\license-obfs4.txt"
- Delete "$INSTDIR\licenses\license-onionshare.txt"
- Delete "$INSTDIR\licenses\license-tor.txt"
- Delete "$INSTDIR\licenses\readme.txt"
- Delete "$INSTDIR\mfc140u.dll"
- Delete "$INSTDIR\MSVCP140.dll"
- Delete "$INSTDIR\onionshare-gui.exe"
- Delete "$INSTDIR\onionshare-gui.exe.manifest"
- Delete "$INSTDIR\pyexpat.pyd"
- Delete "$INSTDIR\PyQt5\Qt\bin\qt.conf"
- Delete "$INSTDIR\PyQt5\Qt\plugins\iconengines\qsvgicon.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qgif.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qicns.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qico.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qjpeg.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qsvg.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qtga.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qtiff.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qwbmp.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qwebp.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qminimal.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qoffscreen.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qwindows.dll"
- Delete "$INSTDIR\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll"
- Delete "$INSTDIR\PyQt5.Qt.pyd"
- Delete "$INSTDIR\PyQt5.QtCore.pyd"
- Delete "$INSTDIR\PyQt5.QtGui.pyd"
- Delete "$INSTDIR\PyQt5.QtPrintSupport.pyd"
- Delete "$INSTDIR\PyQt5.QtWidgets.pyd"
- Delete "$INSTDIR\python3.dll"
- Delete "$INSTDIR\python36.dll"
- Delete "$INSTDIR\pythoncom36.dll"
- Delete "$INSTDIR\pywintypes36.dll"
- Delete "$INSTDIR\Qt5Core.dll"
- Delete "$INSTDIR\Qt5Gui.dll"
- Delete "$INSTDIR\Qt5PrintSupport.dll"
- Delete "$INSTDIR\Qt5Svg.dll"
- Delete "$INSTDIR\Qt5Widgets.dll"
- Delete "$INSTDIR\select.pyd"
- Delete "$INSTDIR\share\html\404.html"
- Delete "$INSTDIR\share\html\denied.html"
- Delete "$INSTDIR\share\html\index.html"
- Delete "$INSTDIR\share\images\download_completed.png"
- Delete "$INSTDIR\share\images\download_completed_none.png"
- Delete "$INSTDIR\share\images\download_in_progress.png"
- Delete "$INSTDIR\share\images\download_in_progress_none.png"
- Delete "$INSTDIR\share\images\favicon.ico"
- Delete "$INSTDIR\share\images\file_delete.png"
- Delete "$INSTDIR\share\images\info.png"
- Delete "$INSTDIR\share\images\logo.png"
- Delete "$INSTDIR\share\images\logo_transparent.png"
- Delete "$INSTDIR\share\images\logo_grayscale.png"
- Delete "$INSTDIR\share\images\server_started.png"
- Delete "$INSTDIR\share\images\server_stopped.png"
- Delete "$INSTDIR\share\images\server_working.png"
- Delete "$INSTDIR\share\images\settings.png"
- Delete "$INSTDIR\share\images\web_file.png"
- Delete "$INSTDIR\share\images\web_folder.png"
- Delete "$INSTDIR\share\locale\cs.json"
- Delete "$INSTDIR\share\locale\de.json"
- Delete "$INSTDIR\share\locale\en.json"
- Delete "$INSTDIR\share\locale\eo.json"
- Delete "$INSTDIR\share\locale\es.json"
- Delete "$INSTDIR\share\locale\fi.json"
- Delete "$INSTDIR\share\locale\fr.json"
- Delete "$INSTDIR\share\locale\it.json"
- Delete "$INSTDIR\share\locale\nl.json"
- Delete "$INSTDIR\share\locale\no.json"
- Delete "$INSTDIR\share\locale\pt.json"
- Delete "$INSTDIR\share\locale\ru.json"
- Delete "$INSTDIR\share\locale\tr.json"
- Delete "$INSTDIR\share\torrc_template"
- Delete "$INSTDIR\share\torrc_template-windows"
- Delete "$INSTDIR\share\torrc_template-obfs4"
- Delete "$INSTDIR\share\torrc_template-meek_lite_amazon"
- Delete "$INSTDIR\share\torrc_template-meek_lite_azure"
- Delete "$INSTDIR\share\version.txt"
- Delete "$INSTDIR\share\wordlist.txt"
- Delete "$INSTDIR\sip.pyd"
- Delete "$INSTDIR\tor\Data\Tor\geoip"
- Delete "$INSTDIR\tor\Data\Tor\geoip6"
- Delete "$INSTDIR\tor\Tor\libeay32.dll"
- Delete "$INSTDIR\tor\Tor\libevent-2-0-5.dll"
- Delete "$INSTDIR\tor\Tor\libevent_core-2-0-5.dll"
- Delete "$INSTDIR\tor\Tor\libevent_extra-2-0-5.dll"
- Delete "$INSTDIR\tor\Tor\libgcc_s_sjlj-1.dll"
- Delete "$INSTDIR\tor\Tor\libssp-0.dll"
- Delete "$INSTDIR\tor\Tor\obfs4proxy.exe"
- Delete "$INSTDIR\tor\Tor\ssleay32.dll"
- Delete "$INSTDIR\tor\Tor\tor.exe"
- Delete "$INSTDIR\tor\Tor\zlib1.dll"
- Delete "$INSTDIR\ucrtbase.dll"
- Delete "$INSTDIR\unicodedata.pyd"
- Delete "$INSTDIR\VCRUNTIME140.dll"
- Delete "$INSTDIR\win32api.pyd"
- Delete "$INSTDIR\win32com.shell.shell.pyd"
- Delete "$INSTDIR\win32trace.pyd"
- Delete "$INSTDIR\win32ui.pyd"
- Delete "$INSTDIR\win32wnet.pyd"
- Delete "$INSTDIR\_asyncio.pyd"
- Delete "$INSTDIR\_bz2.pyd"
- Delete "$INSTDIR\_ctypes.pyd"
- Delete "$INSTDIR\_decimal.pyd"
- Delete "$INSTDIR\_hashlib.pyd"
- Delete "$INSTDIR\_lzma.pyd"
- Delete "$INSTDIR\_multiprocessing.pyd"
- Delete "$INSTDIR\_overlapped.pyd"
- Delete "$INSTDIR\_socket.pyd"
- Delete "$INSTDIR\_ssl.pyd"
- Delete "$INSTDIR\_win32sysloader.pyd"
-
- Delete "$INSTDIR\onionshare.ico"
- Delete "$INSTDIR\uninstall.exe"
-
- rmDir "$INSTDIR\Include"
- rmDir "$INSTDIR\lib2to3\tests\data"
- rmDir "$INSTDIR\lib2to3\tests"
- rmDir "$INSTDIR\lib2to3"
- rmDir "$INSTDIR\licenses"
- rmDir "$INSTDIR\PyQt5\Qt\bin"
- rmDir "$INSTDIR\PyQt5\Qt\plugins\iconengines"
- rmDir "$INSTDIR\PyQt5\Qt\plugins\imageformats"
- rmDir "$INSTDIR\PyQt5\Qt\plugins\platforms"
- rmDir "$INSTDIR\PyQt5\Qt\plugins\printsupport"
- rmDir "$INSTDIR\PyQt5\Qt\plugins"
- rmDir "$INSTDIR\PyQt5\Qt"
- rmDir "$INSTDIR\PyQt5"
- rmDir "$INSTDIR\share\html"
- rmDir "$INSTDIR\share\images"
- rmDir "$INSTDIR\share\locale"
- rmDir "$INSTDIR\share"
- rmDir "$INSTDIR\tor\Data\Tor"
- rmDir "$INSTDIR\tor\Data"
- rmDir "$INSTDIR\tor\Tor"
- rmDir "$INSTDIR\tor"
- rmDir "$INSTDIR"
+ RMDir /r $INSTDIR
# remove uninstaller information from the registry
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec
index 9c233cf4..24664bf9 100644
--- a/install/pyinstaller.spec
+++ b/install/pyinstaller.spec
@@ -14,12 +14,14 @@ a = Analysis(
('../share/wordlist.txt', 'share'),
('../share/torrc_template', 'share'),
('../share/torrc_template-obfs4', 'share'),
- ('../share/torrc_template-meek_lite_amazon', 'share'),
('../share/torrc_template-meek_lite_azure', 'share'),
- ('../share/torrc_template-windows', 'share'),
('../share/images/*', 'share/images'),
('../share/locale/*', 'share/locale'),
- ('../share/html/*', 'share/html'),
+ ('../share/static/*', 'share/static'),
+ ('../share/templates/*', 'share/templates'),
+ ('../share/static/css/*', 'share/static/css'),
+ ('../share/static/img/*', 'share/static/img'),
+ ('../share/static/js/*', 'share/static/js'),
('../install/licenses/*', 'licenses')
],
hiddenimports=[],
diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt
new file mode 100644
index 00000000..89f37572
--- /dev/null
+++ b/install/requirements-tests.txt
@@ -0,0 +1,10 @@
+atomicwrites==1.2.1
+attrs==18.2.0
+more-itertools==4.3.0
+pluggy==0.8.0
+py==1.7.0
+pytest==4.0.1
+pytest-faulthandler==1.5.0
+pytest-qt==3.2.1
+six==1.11.0
+urllib3==1.24.1
diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt
deleted file mode 100644
index 6e03f6e8..00000000
--- a/install/requirements-windows.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-click==6.7
-Flask==0.12.2
-future==0.16.0
-itsdangerous==0.24
-Jinja2==2.10
-MarkupSafe==1.0
-pefile==2017.11.5
-PyInstaller==3.3.1
-PyQt5==5.9.2
-PySocks==1.6.7
-requests==2.19.1
-sip==4.19.6
-stem==1.6.0
-Werkzeug==0.14.1
diff --git a/install/requirements.txt b/install/requirements.txt
index ed83b995..76dfb1ef 100644
--- a/install/requirements.txt
+++ b/install/requirements.txt
@@ -1,12 +1,25 @@
-click==6.7
-Flask==0.12.2
-itsdangerous==0.24
+altgraph==0.16.1
+asn1crypto==0.24.0
+certifi==2018.10.15
+cffi==1.11.5
+chardet==3.0.4
+Click==7.0
+cryptography==2.4.2
+Flask==1.0.2
+future==0.17.1
+idna==2.7
+itsdangerous==1.1.0
Jinja2==2.10
-MarkupSafe==1.0
-PyInstaller==3.3.1
-PyQt5==5.9.2
-PySocks==1.6.7
-requests==2.19.1
-sip==4.19.6
-stem==1.6.0
+macholib==1.11
+MarkupSafe==1.1.0
+pefile==2018.8.8
+pycparser==2.19
+pycryptodome==3.7.2
+PyQt5==5.11.3
+PyQt5-sip==4.19.13
+PySocks==1.6.8
+requests==2.20.1
+six==1.11.0
+stem==1.7.0
+urllib3==1.24.1
Werkzeug==0.14.1
diff --git a/install/scripts/onionshare b/install/scripts/onionshare
index 6c560aec..e2205e04 100755
--- a/install/scripts/onionshare
+++ b/install/scripts/onionshare
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/install/scripts/onionshare-gui b/install/scripts/onionshare-gui
index 6b9fb21b..fed29d83 100755
--- a/install/scripts/onionshare-gui
+++ b/install/scripts/onionshare-gui
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/install/scripts/onionshare-pyinstaller b/install/scripts/onionshare-pyinstaller
index d026e1bf..bd59b421 100644
--- a/install/scripts/onionshare-pyinstaller
+++ b/install/scripts/onionshare-pyinstaller
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/onionshare/__init__.py b/onionshare/__init__.py
index 032b4acc..2f44c846 100644
--- a/onionshare/__init__.py
+++ b/onionshare/__init__.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,21 +20,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, sys, time, argparse, threading
-from . import strings, common, web
+from . import strings
+from .common import Common
+from .web import Web
from .onion import *
from .onionshare import OnionShare
-from .settings import Settings
def main(cwd=None):
"""
The main() function implements all of the logic that the command-line version of
onionshare uses.
"""
+ common = Common()
+
+ # Load the default settings and strings early, for the sake of being able to parse options.
+ # These won't be in the user's chosen locale necessarily, but we need to parse them
+ # early in order to even display the option to pass alternate settings (which might
+ # contain a preferred locale).
+ # If an alternate --config is passed, we'll reload strings later.
+ common.load_settings()
strings.load_strings(common)
- print(strings._('version_string').format(common.get_version()))
+
+ # Display OnionShare banner
+ print(strings._('version_string').format(common.version))
# OnionShare CLI in OSX needs to change current working directory (#132)
- if common.get_platform() == 'Darwin':
+ if common.platform == 'Darwin':
if cwd:
os.chdir(cwd)
@@ -44,9 +55,10 @@ def main(cwd=None):
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
parser.add_argument('--shutdown-timeout', metavar='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout"))
parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth"))
- parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
+ parser.add_argument('--receive', action='store_true', dest='receive', help=strings._("help_receive"))
parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config'))
- parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
+ parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
+ parser.add_argument('filename', metavar='filename', nargs='*', help=strings._('help_filename'))
args = parser.parse_args()
filenames = args.filename
@@ -58,65 +70,86 @@ def main(cwd=None):
stay_open = bool(args.stay_open)
shutdown_timeout = int(args.shutdown_timeout)
stealth = bool(args.stealth)
+ receive = bool(args.receive)
config = args.config
- # Debug mode?
- if debug:
- common.set_debug(debug)
- web.debug_mode()
-
- # Validation
- valid = True
- for filename in filenames:
- if not os.path.isfile(filename) and not os.path.isdir(filename):
- print(strings._("not_a_file").format(filename))
- valid = False
- if not os.access(filename, os.R_OK):
- print(strings._("not_a_readable_file").format(filename))
- valid = False
- if not valid:
+ if receive:
+ mode = 'receive'
+ else:
+ mode = 'share'
+
+ # Make sure filenames given if not using receiver mode
+ if mode == 'share' and len(filenames) == 0:
+ parser.print_help()
sys.exit()
+ # Validate filenames
+ if mode == 'share':
+ valid = True
+ for filename in filenames:
+ if not os.path.isfile(filename) and not os.path.isdir(filename):
+ print(strings._("not_a_file").format(filename))
+ valid = False
+ if not os.access(filename, os.R_OK):
+ print(strings._("not_a_readable_file").format(filename))
+ valid = False
+ if not valid:
+ sys.exit()
+
+ # Re-load settings, if a custom config was passed in
+ if config:
+ common.load_settings(config)
+ # Re-load the strings, in case the provided config has changed locale
+ strings.load_strings(common)
- settings = Settings(config)
+ # Debug mode?
+ common.debug = debug
+
+ # Create the Web object
+ web = Web(common, False, mode)
# Start the Onion object
- onion = Onion()
+ onion = Onion(common)
try:
- onion.connect(settings=False, config=config)
- except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
- sys.exit(e.args[0])
+ onion.connect(custom_settings=False, config=config)
except KeyboardInterrupt:
print("")
sys.exit()
+ except Exception as e:
+ sys.exit(e.args[0])
# Start the onionshare app
try:
- app = OnionShare(onion, local_only, stay_open, shutdown_timeout)
+ app = OnionShare(common, onion, local_only, shutdown_timeout)
app.set_stealth(stealth)
+ app.choose_port()
app.start_onion_service()
except KeyboardInterrupt:
print("")
sys.exit()
+ except (TorTooOld, TorErrorProtocolError) as e:
+ print("")
+ print(e.args[0])
+ sys.exit()
- # Prepare files to share
- print(strings._("preparing_files"))
- try:
- web.set_file_info(filenames)
- app.cleanup_filenames.append(web.zip_filename)
- except OSError as e:
- print(e.strerror)
- sys.exit(1)
-
- # Warn about sending large files over Tor
- if web.zip_filesize >= 157286400: # 150mb
- print('')
- print(strings._("large_filesize"))
- print('')
+ if mode == 'share':
+ # Prepare files to share
+ print(strings._("preparing_files"))
+ try:
+ web.share_mode.set_file_info(filenames)
+ app.cleanup_filenames += web.share_mode.cleanup_filenames
+ except OSError as e:
+ print(e.strerror)
+ sys.exit(1)
+
+ # Warn about sending large files over Tor
+ if web.share_mode.download_filesize >= 157286400: # 150mb
+ print('')
+ print(strings._("large_filesize"))
+ print('')
# Start OnionShare http service in new thread
- settings.load()
- t = threading.Thread(target=web.start, args=(app.port, app.stay_open, settings.get('slug')))
+ t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug')))
t.daemon = True
t.start()
@@ -129,18 +162,39 @@ def main(cwd=None):
app.shutdown_timer.start()
# Save the web slug if we are using a persistent private key
- if settings.get('save_private_key'):
- if not settings.get('slug'):
- settings.set('slug', web.slug)
- settings.save()
-
- if(stealth):
- print(strings._("give_this_url_stealth"))
- print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
- print(app.auth_string)
+ if common.settings.get('save_private_key'):
+ if not common.settings.get('slug'):
+ common.settings.set('slug', web.slug)
+ common.settings.save()
+
+ # Build the URL
+ if common.settings.get('public_mode'):
+ url = 'http://{0:s}'.format(app.onion_host)
+ else:
+ url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
+
+ print('')
+ if mode == 'receive':
+ print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir')))
+ print('')
+ print(strings._('receive_mode_warning'))
+ print('')
+
+ if stealth:
+ print(strings._("give_this_url_receive_stealth"))
+ print(url)
+ print(app.auth_string)
+ else:
+ print(strings._("give_this_url_receive"))
+ print(url)
else:
- print(strings._("give_this_url"))
- print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
+ if stealth:
+ print(strings._("give_this_url_stealth"))
+ print(url)
+ print(app.auth_string)
+ else:
+ print(strings._("give_this_url"))
+ print(url)
print('')
print(strings._("ctrlc_to_stop"))
@@ -149,11 +203,19 @@ def main(cwd=None):
if app.shutdown_timeout > 0:
# if the shutdown timer was set and has run out, stop the server
if not app.shutdown_timer.is_alive():
- # If there were no attempts to download the share, or all downloads are done, we can stop
- if web.download_count == 0 or web.done:
- print(strings._("close_on_timeout"))
- web.stop(app.port)
- break
+ if mode == 'share':
+ # If there were no attempts to download the share, or all downloads are done, we can stop
+ if web.share_mode.download_count == 0 or web.done:
+ print(strings._("close_on_timeout"))
+ web.stop(app.port)
+ break
+ if mode == 'receive':
+ if web.receive_mode.upload_count == 0 or not web.receive_mode.uploads_in_progress:
+ print(strings._("close_on_timeout"))
+ web.stop(app.port)
+ break
+ else:
+ web.receive_mode.can_upload = False
# Allow KeyboardInterrupt exception to be handled with threads
# https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception
time.sleep(0.2)
diff --git a/onionshare/common.py b/onionshare/common.py
index ca6c667d..fcb9ca6d 100644
--- a/onionshare/common.py
+++ b/onionshare/common.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -28,262 +28,476 @@ import sys
import tempfile
import threading
import time
-import zipfile
-debug = False
+from .settings import Settings
-def log(module, func, msg=None):
+class Common(object):
"""
- If debug mode is on, log error messages to stdout
+ The Common object is shared amongst all parts of OnionShare.
"""
- global debug
- if debug:
- timestamp = time.strftime("%b %d %Y %X")
+ def __init__(self, debug=False):
+ self.debug = debug
- final_msg = "[{}] {}.{}".format(timestamp, module, func)
- if msg:
- final_msg = '{}: {}'.format(final_msg, msg)
- print(final_msg)
+ # The platform OnionShare is running on
+ self.platform = platform.system()
+ if self.platform.endswith('BSD'):
+ self.platform = 'BSD'
+ # The current version of OnionShare
+ with open(self.get_resource_path('version.txt')) as f:
+ self.version = f.read().strip()
-def set_debug(new_debug):
- global debug
- debug = new_debug
-
-
-def get_platform():
- """
- Returns the platform OnionShare is running on.
- """
- plat = platform.system()
- if plat.endswith('BSD'):
- plat = 'BSD'
- return plat
-
-
-def get_resource_path(filename):
- """
- Returns the absolute path of a resource, regardless of whether OnionShare is installed
- systemwide, and whether regardless of platform
- """
- p = get_platform()
-
- # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
- if p == 'Windows':
- filename = filename.replace('/', '\\')
-
- if getattr(sys, 'onionshare_dev_mode', False):
- # Look for resources directory relative to python file
- prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share')
- if not os.path.exists(prefix):
- # While running tests during stdeb bdist_deb, look 3 directories up for the share folder
- prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
-
- elif p == 'BSD' or p == 'Linux':
- # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
- prefix = os.path.join(sys.prefix, 'share/onionshare')
-
- elif getattr(sys, 'frozen', False):
- # Check if app is "frozen"
- # https://pythonhosted.org/PyInstaller/#run-time-information
- if p == 'Darwin':
- prefix = os.path.join(sys._MEIPASS, 'share')
- elif p == 'Windows':
- prefix = os.path.join(os.path.dirname(sys.executable), 'share')
-
- return os.path.join(prefix, filename)
-
-
-def get_tor_paths():
- p = get_platform()
- if p == 'Linux':
- tor_path = '/usr/bin/tor'
- tor_geo_ip_file_path = '/usr/share/tor/geoip'
- tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
- obfs4proxy_file_path = '/usr/bin/obfs4proxy'
- elif p == 'Windows':
- base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor')
- tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
- obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
- tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
- tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
- elif p == 'Darwin':
- base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path(''))))
- tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
- tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
- tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
- obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
- elif p == 'BSD':
- tor_path = '/usr/local/bin/tor'
- tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
- tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
- obfs4proxy_file_path = '/usr/local/bin/obfs4proxy'
-
- return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
-
-
-def get_version():
- """
- Returns the version of OnionShare that is running.
- """
- with open(get_resource_path('version.txt')) as f:
- version = f.read().strip()
- return version
-
-
-def random_string(num_bytes, output_len=None):
- """
- Returns a random string with a specified number of bytes.
- """
- b = os.urandom(num_bytes)
- h = hashlib.sha256(b).digest()[:16]
- s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
- if not output_len:
- return s
- return s[:output_len]
-
-
-def build_slug():
- """
- Returns a random string made from two words from the wordlist, such as "deter-trig".
- """
- with open(get_resource_path('wordlist.txt')) as f:
- wordlist = f.read().split()
+ def load_settings(self, config=None):
+ """
+ Loading settings, optionally from a custom config json file.
+ """
+ self.settings = Settings(self, config)
+ self.settings.load()
- r = random.SystemRandom()
- return '-'.join(r.choice(wordlist) for _ in range(2))
+ def log(self, module, func, msg=None):
+ """
+ If debug mode is on, log error messages to stdout
+ """
+ if self.debug:
+ timestamp = time.strftime("%b %d %Y %X")
+ final_msg = "[{}] {}.{}".format(timestamp, module, func)
+ if msg:
+ final_msg = '{}: {}'.format(final_msg, msg)
+ print(final_msg)
-def human_readable_filesize(b):
- """
- Returns filesize in a human readable format.
- """
- thresh = 1024.0
- if b < thresh:
- return '{:.1f} B'.format(b)
- units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
- u = 0
- b /= thresh
- while b >= thresh:
- b /= thresh
- u += 1
- return '{:.1f} {}'.format(b, units[u])
-
-
-def format_seconds(seconds):
- """Return a human-readable string of the format 1d2h3m4s"""
- days, seconds = divmod(seconds, 86400)
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
-
- human_readable = []
- if days:
- human_readable.append("{:.0f}d".format(days))
- if hours:
- human_readable.append("{:.0f}h".format(hours))
- if minutes:
- human_readable.append("{:.0f}m".format(minutes))
- if seconds or not human_readable:
- human_readable.append("{:.0f}s".format(seconds))
- return ''.join(human_readable)
-
-
-def estimated_time_remaining(bytes_downloaded, total_bytes, started):
- now = time.time()
- time_elapsed = now - started # in seconds
- download_rate = bytes_downloaded / time_elapsed
- remaining_bytes = total_bytes - bytes_downloaded
- eta = remaining_bytes / download_rate
- return format_seconds(eta)
-
-
-def get_available_port(min_port, max_port):
- """
- Find a random available port within the given range.
- """
- with socket.socket() as tmpsock:
- while True:
+ def get_resource_path(self, filename):
+ """
+ Returns the absolute path of a resource, regardless of whether OnionShare is installed
+ systemwide, and whether regardless of platform
+ """
+ # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
+ if self.platform == 'Windows':
+ filename = filename.replace('/', '\\')
+
+ if getattr(sys, 'onionshare_dev_mode', False):
+ # Look for resources directory relative to python file
+ prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share')
+ if not os.path.exists(prefix):
+ # While running tests during stdeb bdist_deb, look 3 directories up for the share folder
+ prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
+
+ elif self.platform == 'BSD' or self.platform == 'Linux':
+ # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
+ prefix = os.path.join(sys.prefix, 'share/onionshare')
+
+ elif getattr(sys, 'frozen', False):
+ # Check if app is "frozen"
+ # https://pythonhosted.org/PyInstaller/#run-time-information
+ if self.platform == 'Darwin':
+ prefix = os.path.join(sys._MEIPASS, 'share')
+ elif self.platform == 'Windows':
+ prefix = os.path.join(os.path.dirname(sys.executable), 'share')
+
+ return os.path.join(prefix, filename)
+
+ def get_tor_paths(self):
+ if self.platform == 'Linux':
+ tor_path = '/usr/bin/tor'
+ tor_geo_ip_file_path = '/usr/share/tor/geoip'
+ tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
+ obfs4proxy_file_path = '/usr/bin/obfs4proxy'
+ elif self.platform == 'Windows':
+ base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor')
+ tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
+ obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
+ tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
+ tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
+ elif self.platform == 'Darwin':
+ base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path(''))))
+ tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
+ tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
+ tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
+ obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
+ elif self.platform == 'BSD':
+ tor_path = '/usr/local/bin/tor'
+ tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
+ tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
+ obfs4proxy_file_path = '/usr/local/bin/obfs4proxy'
+
+ return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
+
+ def build_data_dir(self):
+ """
+ Returns the path of the OnionShare data directory.
+ """
+ if self.platform == 'Windows':
try:
- tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
- break
- except OSError as e:
- raise OSError(e)
- _, port = tmpsock.getsockname()
- return port
-
-
-def dir_size(start_path):
- """
- Calculates the total size, in bytes, of all of the files in a directory.
- """
- total_size = 0
- for dirpath, dirnames, filenames in os.walk(start_path):
- for f in filenames:
- fp = os.path.join(dirpath, f)
- if not os.path.islink(fp):
- total_size += os.path.getsize(fp)
- return total_size
-
-
-class ZipWriter(object):
- """
- ZipWriter accepts files and directories and compresses them into a zip file
- with. If a zip_filename is not passed in, it will use the default onionshare
- filename.
- """
- def __init__(self, zip_filename=None, processed_size_callback=None):
- if zip_filename:
- self.zip_filename = zip_filename
+ appdata = os.environ['APPDATA']
+ onionshare_data_dir = '{}\\OnionShare'.format(appdata)
+ except:
+ # If for some reason we don't have the 'APPDATA' environment variable
+ # (like running tests in Linux while pretending to be in Windows)
+ onionshare_data_dir = os.path.expanduser('~/.config/onionshare')
+ elif self.platform == 'Darwin':
+ onionshare_data_dir = os.path.expanduser('~/Library/Application Support/OnionShare')
else:
- self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), random_string(4, 6))
+ onionshare_data_dir = os.path.expanduser('~/.config/onionshare')
- self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
- self.processed_size_callback = processed_size_callback
- if self.processed_size_callback is None:
- self.processed_size_callback = lambda _: None
- self._size = 0
- self.processed_size_callback(self._size)
+ os.makedirs(onionshare_data_dir, 0o700, True)
+ return onionshare_data_dir
- def add_file(self, filename):
+ def build_slug(self):
"""
- Add a file to the zip archive.
+ Returns a random string made from two words from the wordlist, such as "deter-trig".
"""
- self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
- self._size += os.path.getsize(filename)
- self.processed_size_callback(self._size)
+ with open(self.get_resource_path('wordlist.txt')) as f:
+ wordlist = f.read().split()
- def add_dir(self, filename):
+ r = random.SystemRandom()
+ return '-'.join(r.choice(wordlist) for _ in range(2))
+
+ def define_css(self):
"""
- Add a directory, and all of its children, to the zip archive.
+ This defines all of the stylesheets used in GUI mode, to avoid repeating code.
+ This method is only called in GUI mode.
"""
- dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
- for dirpath, dirnames, filenames in os.walk(filename):
- for f in filenames:
- full_filename = os.path.join(dirpath, f)
- if not os.path.islink(full_filename):
- arc_filename = full_filename[len(dir_to_strip):]
- self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
- self._size += os.path.getsize(full_filename)
- self.processed_size_callback(self._size)
-
- def close(self):
+ self.css = {
+ # OnionShareGui styles
+ 'mode_switcher_selected_style': """
+ QPushButton {
+ color: #ffffff;
+ background-color: #4e064f;
+ border: 0;
+ border-right: 1px solid #69266b;
+ font-weight: bold;
+ border-radius: 0;
+ }""",
+
+ 'mode_switcher_unselected_style': """
+ QPushButton {
+ color: #ffffff;
+ background-color: #601f61;
+ border: 0;
+ font-weight: normal;
+ border-radius: 0;
+ }""",
+
+ 'settings_button': """
+ QPushButton {
+ background-color: #601f61;
+ border: 0;
+ border-left: 1px solid #69266b;
+ border-radius: 0;
+ }""",
+
+ 'server_status_indicator_label': """
+ QLabel {
+ font-style: italic;
+ color: #666666;
+ padding: 2px;
+ }""",
+
+ 'status_bar': """
+ QStatusBar {
+ font-style: italic;
+ color: #666666;
+ }
+ QStatusBar::item {
+ border: 0px;
+ }""",
+
+ # Common styles between ShareMode and ReceiveMode and their child widgets
+ 'mode_info_label': """
+ QLabel {
+ font-size: 12px;
+ color: #666666;
+ }
+ """,
+
+ 'server_status_url': """
+ QLabel {
+ background-color: #ffffff;
+ color: #000000;
+ padding: 10px;
+ border: 1px solid #666666;
+ font-size: 12px;
+ }
+ """,
+
+ 'server_status_url_buttons': """
+ QPushButton {
+ color: #3f7fcf;
+ }
+ """,
+
+ 'server_status_button_stopped': """
+ QPushButton {
+ background-color: #5fa416;
+ color: #ffffff;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ }""",
+
+ 'server_status_button_working': """
+ QPushButton {
+ background-color: #4c8211;
+ color: #ffffff;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ font-style: italic;
+ }""",
+
+ 'server_status_button_started': """
+ QPushButton {
+ background-color: #d0011b;
+ color: #ffffff;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ }""",
+
+ 'downloads_uploads_empty': """
+ QWidget {
+ background-color: #ffffff;
+ border: 1px solid #999999;
+ }
+ QWidget QLabel {
+ background-color: none;
+ border: 0px;
+ }
+ """,
+
+ 'downloads_uploads_empty_text': """
+ QLabel {
+ color: #999999;
+ }""",
+
+ 'downloads_uploads_label': """
+ QLabel {
+ font-weight: bold;
+ font-size 14px;
+ text-align: center;
+ background-color: none;
+ border: none;
+ }""",
+
+ 'downloads_uploads_clear': """
+ QPushButton {
+ color: #3f7fcf;
+ }
+ """,
+
+ 'download_uploads_indicator': """
+ QLabel {
+ color: #ffffff;
+ background-color: #f44449;
+ font-weight: bold;
+ font-size: 10px;
+ padding: 2px;
+ border-radius: 7px;
+ text-align: center;
+ }""",
+
+ 'downloads_uploads_progress_bar': """
+ QProgressBar {
+ border: 1px solid #4e064f;
+ background-color: #ffffff !important;
+ text-align: center;
+ color: #9b9b9b;
+ font-size: 14px;
+ }
+ QProgressBar::chunk {
+ background-color: #4e064f;
+ width: 10px;
+ }""",
+
+ # Share mode and child widget styles
+ 'share_zip_progess_bar': """
+ QProgressBar {
+ border: 1px solid #4e064f;
+ background-color: #ffffff !important;
+ text-align: center;
+ color: #9b9b9b;
+ }
+ QProgressBar::chunk {
+ border: 0px;
+ background-color: #4e064f;
+ width: 10px;
+ }""",
+
+ 'share_filesize_warning': """
+ QLabel {
+ padding: 10px 0;
+ font-weight: bold;
+ color: #333333;
+ }
+ """,
+
+ 'share_file_selection_drop_here_label': """
+ QLabel {
+ color: #999999;
+ }""",
+
+ 'share_file_selection_drop_count_label': """
+ QLabel {
+ color: #ffffff;
+ background-color: #f44449;
+ font-weight: bold;
+ padding: 5px 10px;
+ border-radius: 10px;
+ }""",
+
+ 'share_file_list_drag_enter': """
+ FileList {
+ border: 3px solid #538ad0;
+ }
+ """,
+
+ 'share_file_list_drag_leave': """
+ FileList {
+ border: none;
+ }
+ """,
+
+ 'share_file_list_item_size': """
+ QLabel {
+ color: #666666;
+ font-size: 11px;
+ }""",
+
+ # Receive mode and child widget styles
+ 'receive_file': """
+ QWidget {
+ background-color: #ffffff;
+ }
+ """,
+
+ 'receive_file_size': """
+ QLabel {
+ color: #666666;
+ font-size: 11px;
+ }""",
+
+ # Settings dialog
+ 'settings_version': """
+ QLabel {
+ color: #666666;
+ }""",
+
+ 'settings_tor_status': """
+ QLabel {
+ background-color: #ffffff;
+ color: #000000;
+ padding: 10px;
+ }""",
+
+ 'settings_whats_this': """
+ QLabel {
+ font-size: 12px;
+ }""",
+
+ 'settings_connect_to_tor': """
+ QLabel {
+ font-style: italic;
+ }"""
+ }
+
+ @staticmethod
+ def random_string(num_bytes, output_len=None):
+ """
+ Returns a random string with a specified number of bytes.
+ """
+ b = os.urandom(num_bytes)
+ h = hashlib.sha256(b).digest()[:16]
+ s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
+ if not output_len:
+ return s
+ return s[:output_len]
+
+ @staticmethod
+ def human_readable_filesize(b):
+ """
+ Returns filesize in a human readable format.
+ """
+ thresh = 1024.0
+ if b < thresh:
+ return '{:.1f} B'.format(b)
+ units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
+ u = 0
+ b /= thresh
+ while b >= thresh:
+ b /= thresh
+ u += 1
+ return '{:.1f} {}'.format(b, units[u])
+
+ @staticmethod
+ def format_seconds(seconds):
+ """Return a human-readable string of the format 1d2h3m4s"""
+ days, seconds = divmod(seconds, 86400)
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+
+ human_readable = []
+ if days:
+ human_readable.append("{:.0f}d".format(days))
+ if hours:
+ human_readable.append("{:.0f}h".format(hours))
+ if minutes:
+ human_readable.append("{:.0f}m".format(minutes))
+ if seconds or not human_readable:
+ human_readable.append("{:.0f}s".format(seconds))
+ return ''.join(human_readable)
+
+ @staticmethod
+ def estimated_time_remaining(bytes_downloaded, total_bytes, started):
+ now = time.time()
+ time_elapsed = now - started # in seconds
+ download_rate = bytes_downloaded / time_elapsed
+ remaining_bytes = total_bytes - bytes_downloaded
+ eta = remaining_bytes / download_rate
+ return Common.format_seconds(eta)
+
+ @staticmethod
+ def get_available_port(min_port, max_port):
+ """
+ Find a random available port within the given range.
+ """
+ with socket.socket() as tmpsock:
+ while True:
+ try:
+ tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
+ break
+ except OSError as e:
+ pass
+ _, port = tmpsock.getsockname()
+ return port
+
+ @staticmethod
+ def dir_size(start_path):
"""
- Close the zip archive.
+ Calculates the total size, in bytes, of all of the files in a directory.
"""
- self.z.close()
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ if not os.path.islink(fp):
+ total_size += os.path.getsize(fp)
+ return total_size
-class close_after_seconds(threading.Thread):
+class ShutdownTimer(threading.Thread):
"""
Background thread sleeps t hours and returns.
"""
- def __init__(self, time):
+ def __init__(self, common, time):
threading.Thread.__init__(self)
+
+ self.common = common
+
self.setDaemon(True)
self.time = time
def run(self):
- log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time))
+ self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time))
time.sleep(self.time)
return 1
diff --git a/onionshare/onion.py b/onionshare/onion.py
index 4d159d94..ed4fde7b 100644
--- a/onionshare/onion.py
+++ b/onionshare/onion.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,9 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from stem.control import Controller
from stem import ProtocolError, SocketClosed
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
-import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
+from Crypto.PublicKey import RSA
+import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
-from . import socks
+from distutils.version import LooseVersion as Version
from . import common, strings
from .settings import Settings
@@ -125,38 +126,40 @@ class Onion(object):
call this function and pass in a status string while connecting to tor. This
is necessary for status updates to reach the GUI.
"""
- def __init__(self):
- common.log('Onion', '__init__')
+ def __init__(self, common):
+ self.common = common
+
+ self.common.log('Onion', '__init__')
self.stealth = False
self.service_id = None
- self.system = common.get_platform()
-
# Is bundled tor supported?
- if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
+ if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
self.bundle_tor_supported = False
else:
self.bundle_tor_supported = True
# Set the path of the tor binary, for bundled tor
- (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
+ (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
# The tor process
self.tor_proc = None
+ # The Tor controller
+ self.c = None
+
# Start out not connected to Tor
self.connected_to_tor = False
- def connect(self, settings=False, config=False, tor_status_update_func=None):
- common.log('Onion', 'connect')
+ def connect(self, custom_settings=False, config=False, tor_status_update_func=None):
+ self.common.log('Onion', 'connect')
- # Either use settings that are passed in, or load them from disk
- if settings:
- self.settings = settings
+ # Either use settings that are passed in, or use them from common
+ if custom_settings:
+ self.settings = custom_settings
else:
- self.settings = Settings(config)
- self.settings.load()
+ self.settings = self.common.settings
# The Tor controller
self.c = None
@@ -166,34 +169,35 @@ class Onion(object):
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
# Create a torrc for this session
- self.tor_data_directory = tempfile.TemporaryDirectory()
+ self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
+ self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
- if self.system == 'Windows':
- # Windows needs to use network ports, doesn't support unix sockets
- torrc_template = open(common.get_resource_path('torrc_template-windows')).read()
+ # Create the torrc
+ with open(self.common.get_resource_path('torrc_template')) as f:
+ torrc_template = f.read()
+ self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
+ try:
+ self.tor_socks_port = self.common.get_available_port(1000, 65535)
+ except:
+ raise OSError(strings._('no_available_port'))
+ self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
+
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
+ # Windows doesn't support unix sockets, so it must use a network port.
+ # macOS can't use unix sockets either because socket filenames are limited to
+ # 100 chars, and the macOS sandbox forces us to put the socket file in a place
+ # with a really long path.
+ torrc_template += 'ControlPort {{control_port}}\n'
try:
- self.tor_control_port = common.get_available_port(1000, 65535)
+ self.tor_control_port = self.common.get_available_port(1000, 65535)
except:
raise OSError(strings._('no_available_port'))
self.tor_control_socket = None
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
else:
- # Linux, Mac and BSD can use unix sockets
- with open(common.get_resource_path('torrc_template')) as f:
- torrc_template = f.read()
+ # Linux and BSD can use unix sockets
+ torrc_template += 'ControlSocket {{control_socket}}\n'
self.tor_control_port = None
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
@@ -202,23 +206,19 @@ class Onion(object):
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
+
with open(self.tor_torrc, 'w') as f:
f.write(torrc_template)
# Bridge support
if self.settings.get('tor_bridges_use_obfs4'):
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-obfs4')) as o:
- for line in o:
- f.write(line)
- elif self.settings.get('tor_bridges_use_meek_lite_amazon'):
- f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o:
+ with open(self.common.get_resource_path('torrc_template-obfs4')) as o:
for line in o:
f.write(line)
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o:
+ with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o:
for line in o:
f.write(line)
@@ -232,7 +232,7 @@ class Onion(object):
# Execute a tor subprocess
start_ts = time.time()
- if self.system == 'Windows':
+ if self.common.platform == 'Windows':
# In Windows, hide console window when opening tor.exe subprocess
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
@@ -245,14 +245,14 @@ class Onion(object):
# Connect to the controller
try:
- if self.system == 'Windows':
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
self.c = Controller.from_port(port=self.tor_control_port)
self.c.authenticate()
else:
self.c = Controller.from_socket_file(path=self.tor_control_socket)
self.c.authenticate()
except Exception as e:
- raise BundledTorBroken(strings._('settings_error_bundled_tor_broken', True).format(e.args[0]))
+ raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0]))
while True:
try:
@@ -270,7 +270,7 @@ class Onion(object):
if callable(tor_status_update_func):
if not tor_status_update_func(progress, summary):
# If the dialog was canceled, stop connecting to Tor
- common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
+ self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
print()
return False
@@ -282,7 +282,6 @@ class Onion(object):
# If using bridges, it might take a bit longer to connect to Tor
if self.settings.get('tor_bridges_use_custom_bridges') or \
self.settings.get('tor_bridges_use_obfs4') or \
- self.settings.get('tor_bridges_use_meek_lite_amazon') or \
self.settings.get('tor_bridges_use_meek_lite_azure'):
connect_timeout = 150
else:
@@ -322,7 +321,7 @@ class Onion(object):
socket_file_path = ''
if not found_tor:
try:
- if self.system == 'Darwin':
+ if self.common.platform == 'Darwin':
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
self.c = Controller.from_socket_file(path=socket_file_path)
@@ -334,11 +333,11 @@ class Onion(object):
# guessing the socket file name next
if not found_tor:
try:
- if self.system == 'Linux' or self.system == 'BSD':
+ if self.common.platform == 'Linux' or self.common.platform == 'BSD':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
- elif self.system == 'Darwin':
+ elif self.common.platform == 'Darwin':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
- elif self.system == 'Windows':
+ elif self.common.platform == 'Windows':
# Windows doesn't support unix sockets
raise TorErrorAutomatic(strings._('settings_error_automatic'))
@@ -393,6 +392,7 @@ class Onion(object):
# Get the tor version
self.tor_version = self.c.get_version().version_str
+ self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version))
# Do the versions of stem and tor that I'm using support ephemeral onion services?
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
@@ -408,6 +408,10 @@ class Onion(object):
# ephemeral stealth onion services are not supported
self.supports_stealth = False
+ # Does this version of Tor support next-gen ('v3') onions?
+ # Note, this is the version of Tor where this bug was fixed:
+ # https://trac.torproject.org/projects/tor/ticket/28619
+ self.supports_v3_onions = self.tor_version >= Version('0.3.5.7')
def is_authenticated(self):
"""
@@ -424,7 +428,7 @@ class Onion(object):
Start a onion service on port 80, pointing to the given port, and
return the onion hostname.
"""
- common.log('Onion', 'start_onion_service')
+ self.common.log('Onion', 'start_onion_service')
self.auth_string = None
if not self.supports_ephemeral:
@@ -433,7 +437,6 @@ class Onion(object):
raise TorTooOld(strings._('error_stealth_not_supported'))
print(strings._("config_onion_service").format(int(port)))
- print(strings._('using_ephemeral'))
if self.stealth:
if self.settings.get('hidservauth_string'):
@@ -445,23 +448,42 @@ class Onion(object):
basic_auth = None
if self.settings.get('private_key'):
- key_type = "RSA1024"
key_content = self.settings.get('private_key')
- common.log('Onion', 'Starting a hidden service with a saved private key')
+ if self.is_v2_key(key_content):
+ key_type = "RSA1024"
+ else:
+ # Assume it was a v3 key. Stem will throw an error if it's something illegible
+ key_type = "ED25519-V3"
+
else:
key_type = "NEW"
- key_content = "RSA1024"
- common.log('Onion', 'Starting a hidden service with a new private key')
+ # Work out if we can support v3 onion services, which are preferred
+ if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'):
+ key_content = "ED25519-V3"
+ else:
+ # fall back to v2 onion services
+ key_content = "RSA1024"
+
+ # v3 onions don't yet support basic auth. Our ticket:
+ # https://github.com/micahflee/onionshare/issues/697
+ if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
+ basic_auth = None
+ self.stealth = False
+ debug_message = 'key_type={}'.format(key_type)
+ if key_type == "NEW":
+ debug_message += ', key_content={}'.format(key_content)
+ self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message))
+ await_publication = True
try:
if basic_auth != None:
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type = key_type, key_content=key_content)
+ res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content)
else:
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type = key_type, key_content=key_content)
+ res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content)
- except ProtocolError:
- raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
+ except ProtocolError as e:
+ raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
self.service_id = res.service_id
onion_host = self.service_id + '.onion'
@@ -492,23 +514,23 @@ class Onion(object):
self.settings.save()
return onion_host
else:
- raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
+ raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown'))
def cleanup(self, stop_tor=True):
"""
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
"""
- common.log('Onion', 'cleanup')
+ self.common.log('Onion', 'cleanup')
# Cleanup the ephemeral onion services, if we have any
try:
onions = self.c.list_ephemeral_hidden_services()
for onion in onions:
try:
- common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
+ self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
self.c.remove_ephemeral_hidden_service(onion)
except:
- common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
+ self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
pass
except:
pass
@@ -545,7 +567,7 @@ class Onion(object):
"""
Returns a (address, port) tuple for the Tor SOCKS port
"""
- common.log('Onion', 'get_tor_socks_port')
+ self.common.log('Onion', 'get_tor_socks_port')
if self.settings.get('connection_type') == 'bundled':
return ('127.0.0.1', self.tor_socks_port)
@@ -553,3 +575,18 @@ class Onion(object):
return ('127.0.0.1', 9150)
else:
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
+
+ def is_v2_key(self, key):
+ """
+ Helper function for determining if a key is RSA1024 (v2) or not.
+ """
+ try:
+ # Import the key
+ key = RSA.importKey(base64.b64decode(key))
+ # Is this a v2 Onion key? (1024 bits) If so, we should keep using it.
+ if key.n.bit_length() == 1024:
+ return True
+ else:
+ return False
+ except:
+ return False
diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py
index b1760537..551b8314 100644
--- a/onionshare/onionshare.py
+++ b/onionshare/onionshare.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,20 +21,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, shutil
from . import common, strings
+from .onion import TorTooOld, TorErrorProtocolError
+from .common import ShutdownTimer
class OnionShare(object):
"""
OnionShare is the main application class. Pass in options and run
start_onion_service and it will do the magic.
"""
- def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0):
- common.log('OnionShare', '__init__')
+ def __init__(self, common, onion, local_only=False, shutdown_timeout=0):
+ self.common = common
+
+ self.common.log('OnionShare', '__init__')
# The Onion object
self.onion = onion
self.hidserv_dir = None
self.onion_host = None
+ self.port = None
self.stealth = None
# files and dirs to delete on shutdown
@@ -43,39 +48,42 @@ class OnionShare(object):
# do not use tor -- for development
self.local_only = local_only
- # automatically close when download is finished
- self.stay_open = stay_open
-
# optionally shut down after N hours
self.shutdown_timeout = shutdown_timeout
# init timing thread
self.shutdown_timer = None
def set_stealth(self, stealth):
- common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth))
+ self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth))
self.stealth = stealth
self.onion.stealth = stealth
- def start_onion_service(self):
+ def choose_port(self):
"""
- Start the onionshare onion service.
+ Choose a random port.
"""
- common.log('OnionShare', 'start_onion_service')
-
- # Choose a random port
try:
- self.port = common.get_available_port(17600, 17650)
+ self.port = self.common.get_available_port(17600, 17650)
except:
raise OSError(strings._('no_available_port'))
+ def start_onion_service(self):
+ """
+ Start the onionshare onion service.
+ """
+ self.common.log('OnionShare', 'start_onion_service')
+
+ if not self.port:
+ self.choose_port()
+
+ if self.shutdown_timeout > 0:
+ self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout)
+
if self.local_only:
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
return
- if self.shutdown_timeout > 0:
- self.shutdown_timer = common.close_after_seconds(self.shutdown_timeout)
-
self.onion_host = self.onion.start_onion_service(self.port)
if self.stealth:
@@ -85,12 +93,16 @@ class OnionShare(object):
"""
Shut everything down and clean up temporary files, etc.
"""
- common.log('OnionShare', 'cleanup')
-
- # cleanup files
- for filename in self.cleanup_filenames:
- if os.path.isfile(filename):
- os.remove(filename)
- elif os.path.isdir(filename):
- shutil.rmtree(filename)
+ self.common.log('OnionShare', 'cleanup')
+
+ # Cleanup files
+ try:
+ for filename in self.cleanup_filenames:
+ if os.path.isfile(filename):
+ os.remove(filename)
+ elif os.path.isdir(filename):
+ shutil.rmtree(filename)
+ except:
+ # Don't crash if file is still in use
+ pass
self.cleanup_filenames = []
diff --git a/onionshare/settings.py b/onionshare/settings.py
index 034e863f..68cbb857 100644
--- a/onionshare/settings.py
+++ b/onionshare/settings.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,8 +21,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import os
import platform
+import locale
-from . import strings, common
+try:
+ # We only need pwd module in macOS, and it's not available in Windows
+ import pwd
+except:
+ pass
+
+from . import strings
class Settings(object):
@@ -32,8 +39,10 @@ class Settings(object):
which is to attempt to connect automatically using default Tor Browser
settings.
"""
- def __init__(self, config=False):
- common.log('Settings', '__init__')
+ def __init__(self, common, config=False):
+ self.common = common
+
+ self.common.log('Settings', '__init__')
# Default config
self.filename = self.build_filename()
@@ -43,11 +52,29 @@ class Settings(object):
if os.path.isfile(config):
self.filename = config
else:
- common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
+ self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
+
+ # Dictionary of available languages in this version of OnionShare,
+ # mapped to the language name, in that language
+ self.available_locales = {
+ 'bn': 'বাংলা', # Bengali
+ 'ca': 'Català', # Catalan
+ 'da': 'Dansk', # Danish
+ 'en': 'English', # English
+ 'fr': 'Français', # French
+ 'el': 'Ελληνικά', # Greek
+ 'it': 'Italiano', # Italian
+ 'ja': '日本語', # Japanese
+ 'fa': 'فارسی', # Persian
+ 'pt_BR': 'Português (Brasil)', # Portuguese Brazil
+ 'ru': 'Русский', # Russian
+ 'es': 'Español', # Spanish
+ 'sv': 'Svenska' # Swedish
+ }
# These are the default settings. They will get overwritten when loading from disk
self.default_settings = {
- 'version': common.get_version(),
+ 'version': self.common.version,
'connection_type': 'bundled',
'control_port_address': '127.0.0.1',
'control_port_port': 9051,
@@ -57,20 +84,22 @@ class Settings(object):
'auth_type': 'no_auth',
'auth_password': '',
'close_after_first_download': True,
- 'systray_notifications': True,
'shutdown_timeout': False,
'use_stealth': False,
'use_autoupdate': True,
'autoupdate_timestamp': None,
'no_bridges': True,
'tor_bridges_use_obfs4': False,
- 'tor_bridges_use_meek_lite_amazon': False,
'tor_bridges_use_meek_lite_azure': False,
'tor_bridges_use_custom_bridges': '',
+ 'use_legacy_v2_onions': False,
'save_private_key': False,
'private_key': '',
+ 'public_mode': False,
'slug': '',
- 'hidservauth_string': ''
+ 'hidservauth_string': '',
+ 'data_dir': self.build_default_data_dir(),
+ 'locale': None # this gets defined in fill_in_defaults()
}
self._settings = {}
self.fill_in_defaults()
@@ -84,47 +113,78 @@ class Settings(object):
if key not in self._settings:
self._settings[key] = self.default_settings[key]
+ # Choose the default locale based on the OS preference, and fall-back to English
+ if self._settings['locale'] is None:
+ language_code, encoding = locale.getdefaultlocale()
+
+ # Default to English
+ if not language_code:
+ language_code = 'en_US'
+
+ if language_code == 'pt_PT' and language_code == 'pt_BR':
+ # Portuguese locales include country code
+ default_locale = language_code
+ else:
+ # All other locales cut off the country code
+ default_locale = language_code[:2]
+
+ if default_locale not in self.available_locales:
+ default_locale = 'en'
+ self._settings['locale'] = default_locale
+
def build_filename(self):
"""
Returns the path of the settings file.
"""
- p = platform.system()
- if p == 'Windows':
- appdata = os.environ['APPDATA']
- return '{}\\OnionShare\\onionshare.json'.format(appdata)
- elif p == 'Darwin':
- return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json')
+ return os.path.join(self.common.build_data_dir(), 'onionshare.json')
+
+ def build_default_data_dir(self):
+ """
+ Returns the path of the default Downloads directory for receive mode.
+ """
+
+ if self.common.platform == "Darwin":
+ # We can't use os.path.expanduser() in macOS because in the sandbox it
+ # returns the path to the sandboxed homedir
+ real_homedir = pwd.getpwuid(os.getuid()).pw_dir
+ return os.path.join(real_homedir, 'OnionShare')
+ elif self.common.platform == "Windows":
+ # On Windows, os.path.expanduser() needs to use backslash, or else it
+ # retains the forward slash, which breaks opening the folder in explorer.
+ return os.path.expanduser('~\OnionShare')
else:
- return os.path.expanduser('~/.config/onionshare/onionshare.json')
+ # All other OSes
+ return os.path.expanduser('~/OnionShare')
def load(self):
"""
Load the settings from file.
"""
- common.log('Settings', 'load')
+ self.common.log('Settings', 'load')
# If the settings file exists, load it
if os.path.exists(self.filename):
try:
- common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
+ self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
with open(self.filename, 'r') as f:
self._settings = json.load(f)
self.fill_in_defaults()
except:
pass
+ # Make sure data_dir exists
+ try:
+ os.makedirs(self.get('data_dir'), exist_ok=True)
+ except:
+ pass
+
def save(self):
"""
Save settings to file.
"""
- common.log('Settings', 'save')
-
- try:
- os.makedirs(os.path.dirname(self.filename))
- except:
- pass
+ self.common.log('Settings', 'save')
open(self.filename, 'w').write(json.dumps(self._settings))
- print(strings._('settings_saved').format(self.filename))
+ self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
def get(self, key):
return self._settings[key]
diff --git a/onionshare/socks.py b/onionshare/socks.py
deleted file mode 100644
index 809a4444..00000000
--- a/onionshare/socks.py
+++ /dev/null
@@ -1,530 +0,0 @@
-"""
-SocksiPy - Python SOCKS module.
-Version 1.5.0
-
-Copyright 2006 Dan-Haim. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-3. Neither the name of Dan Haim nor the names of his contributors may be used
- to endorse or promote products derived from this software without specific
- prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-This module provides a standard socket-like interface for Python
-for tunneling connections through SOCKS proxies.
-
-===============================================================================
-
-Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
-for use in PyLoris (http://pyloris.sourceforge.net/)
-
-Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
-mainly to merge bug fixes found in Sourceforge
-
-Modifications made by Anorov (https://github.com/Anorov)
--Forked and renamed to PySocks
--Fixed issue with HTTP proxy failure checking (same bug that was in the old ___recvall() method)
--Included SocksiPyHandler (sockshandler.py), to be used as a urllib2 handler,
- courtesy of e000 (https://github.com/e000): https://gist.github.com/869791#file_socksipyhandler.py
--Re-styled code to make it readable
- -Aliased PROXY_TYPE_SOCKS5 -> SOCKS5 etc.
- -Improved exception handling and output
- -Removed irritating use of sequence indexes, replaced with tuple unpacked variables
- -Fixed up Python 3 bytestring handling - chr(0x03).encode() -> b"\x03"
- -Other general fixes
--Added clarification that the HTTP proxy connection method only supports CONNECT-style tunneling HTTP proxies
--Various small bug fixes
-"""
-
-__version__ = "1.5.0"
-
-import socket
-import struct
-
-PROXY_TYPE_SOCKS4 = SOCKS4 = 1
-PROXY_TYPE_SOCKS5 = SOCKS5 = 2
-PROXY_TYPE_HTTP = HTTP = 3
-
-PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"}
-
-_orgsocket = _orig_socket = socket.socket
-
-
-class ProxyError(IOError):
- """
- socket_err contains original socket.error exception.
- """
- def __init__(self, msg, socket_err=None):
- self.msg = msg
- self.socket_err = socket_err
-
- if socket_err:
- self.msg = msg + ": {}".format(socket_err)
-
- def __str__(self):
- return self.msg
-
-
-class GeneralProxyError(ProxyError):
- pass
-
-
-class ProxyConnectionError(ProxyError):
- pass
-
-
-class SOCKS5AuthError(ProxyError):
- pass
-
-
-class SOCKS5Error(ProxyError):
- pass
-
-
-class SOCKS4Error(ProxyError):
- pass
-
-
-class HTTPError(ProxyError):
- pass
-
-
-SOCKS4_ERRORS = {
- 0x5B: "Request rejected or failed",
- 0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
- 0x5D: "Request rejected because the client program and identd report different user-ids",
-}
-
-SOCKS5_ERRORS = {
- 0x01: "General SOCKS server failure",
- 0x02: "Connection not allowed by ruleset",
- 0x03: "Network unreachable",
- 0x04: "Host unreachable",
- 0x05: "Connection refused",
- 0x06: "TTL expired",
- 0x07: "Command not supported, or protocol error",
- 0x08: "Address type not supported",
-}
-
-DEFAULT_PORTS = {
- SOCKS4: 1080,
- SOCKS5: 1080,
- HTTP: 8080,
-}
-
-
-def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
- """
- set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
-
- Sets a default proxy which all further socksocket objects will use,
- unless explicitly changed.
- """
- socksocket.default_proxy = (proxy_type, addr.encode(), port, rdns,
- username.encode() if username else None,
- password.encode() if password else None)
-
-setdefaultproxy = set_default_proxy
-
-
-def get_default_proxy():
- """
- Returns the default proxy, set by set_default_proxy.
- """
- return socksocket.default_proxy
-
-getdefaultproxy = get_default_proxy
-
-
-def wrap_module(module):
- """
- Attempts to replace a module's socket library with a SOCKS socket. Must set
- a default proxy using set_default_proxy(...) first.
- This will only work on modules that import socket directly into the namespace;
- most of the Python Standard Library falls into this category.
- """
- if socksocket.default_proxy:
- module.socket.socket = socksocket
- else:
- raise GeneralProxyError("No default proxy specified")
-
-wrapmodule = wrap_module
-
-
-def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
- proxy_port=None, proxy_username=None,
- proxy_password=None, timeout=None):
- """create_connection(dest_pair, **proxy_args) -> socket object
-
- Like socket.create_connection(), but connects to proxy
- before returning the socket object.
-
- dest_pair - 2-tuple of (IP/hostname, port).
- **proxy_args - Same args passed to socksocket.set_proxy().
- timeout - Optional socket timeout value, in seconds.
- """
- sock = socksocket()
- if isinstance(timeout, (int, float)):
- sock.settimeout(timeout)
- sock.set_proxy(proxy_type, proxy_addr, proxy_port,
- proxy_username, proxy_password)
- sock.connect(dest_pair)
- return sock
-
-
-class socksocket(socket.socket):
- """socksocket([family[, type[, proto]]]) -> socket object
-
- Open a SOCKS enabled socket. The parameters are the same as
- those of the standard socket init. In order for SOCKS to work,
- you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
- """
-
- default_proxy = None
-
- def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
- _orig_socket.__init__(self, family, type, proto, _sock)
-
- if self.default_proxy:
- self.proxy = self.default_proxy
- else:
- self.proxy = (None, None, None, None, None, None)
- self.proxy_sockname = None
- self.proxy_peername = None
-
- self.proxy_negotiators = {
- SOCKS4: self._negotiate_SOCKS4,
- SOCKS5: self._negotiate_SOCKS5,
- HTTP: self._negotiate_HTTP,
- }
-
- def _recvall(self, count):
- """
- Receive EXACTLY the number of bytes requested from the socket.
- Blocks until the required number of bytes have been received.
- """
- data = b""
- while len(data) < count:
- d = self.recv(count - len(data))
- if not d:
- raise GeneralProxyError("Connection closed unexpectedly")
- data += d
- return data
-
- def set_proxy(self, proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
- """set_proxy(proxy_type, addr[, port[, rdns[, username[, password]]]])
- Sets the proxy to be used.
-
- proxy_type - The type of the proxy to be used. Three types
- are supported: PROXY_TYPE_SOCKS4 (including socks4a),
- PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
- addr - The address of the server (IP or DNS).
- port - The port of the server. Defaults to 1080 for SOCKS
- servers and 8080 for HTTP proxy servers.
- rdns - Should DNS queries be performed on the remote side
- (rather than the local side). The default is True.
- Note: This has no effect with SOCKS4 servers.
- username - Username to authenticate with to the server.
- The default is no authentication.
- password - Password to authenticate with to the server.
- Only relevant when username is also provided.
- """
- self.proxy = (proxy_type, addr.encode(), port, rdns,
- username.encode() if username else None,
- password.encode() if password else None)
-
- setproxy = set_proxy
-
- def get_proxy_sockname(self):
- """
- Returns the bound IP address and port number at the proxy.
- """
- return self.proxy_sockname
-
- getproxysockname = get_proxy_sockname
-
- def get_proxy_peername(self):
- """
- Returns the IP and port number of the proxy.
- """
- return _orig_socket.getpeername(self)
-
- getproxypeername = get_proxy_peername
-
- def get_peername(self):
- """
- Returns the IP address and port number of the destination
- machine (note: get_proxy_peername returns the proxy)
- """
- return self.proxy_peername
-
- getpeername = get_peername
-
- def _negotiate_SOCKS5(self, dest_addr, dest_port):
- """
- Negotiates a connection through a SOCKS5 server.
- """
- proxy_type, addr, port, rdns, username, password = self.proxy
-
- # First we'll send the authentication packages we support.
- if username and password:
- # The username/password details were supplied to the
- # set_proxy method so we support the USERNAME/PASSWORD
- # authentication (in addition to the standard none).
- self.sendall(b"\x05\x02\x00\x02")
- else:
- # No username/password were entered, therefore we
- # only support connections with no authentication.
- self.sendall(b"\x05\x01\x00")
-
- # We'll receive the server's response to determine which
- # method was selected
- chosen_auth = self._recvall(2)
-
- if chosen_auth[0:1] != b"\x05":
- # Note: string[i:i+1] is used because indexing of a bytestring
- # via bytestring[i] yields an integer in Python 3
- raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
-
- # Check the chosen authentication method
-
- if chosen_auth[1:2] == b"\x02":
- # Okay, we need to perform a basic username/password
- # authentication.
- self.sendall(b"\x01" + chr(len(username)).encode()
- + username
- + chr(len(password)).encode()
- + password)
- auth_status = self._recvall(2)
- if auth_status[0:1] != b"\x01":
- # Bad response
- raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
- if auth_status[1:2] != b"\x00":
- # Authentication failed
- raise SOCKS5AuthError("SOCKS5 authentication failed")
-
- # Otherwise, authentication succeeded
-
- # No authentication is required if 0x00
- elif chosen_auth[1:2] != b"\x00":
- # Reaching here is always bad
- if chosen_auth[1:2] == b"\xFF":
- raise SOCKS5AuthError("All offered SOCKS5 authentication methods were rejected")
- else:
- raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
-
- # Now we can request the actual connection
- req = b"\x05\x01\x00"
- # If the given destination address is an IP address, we'll
- # use the IPv4 address request even if remote resolving was specified.
- try:
- addr_bytes = socket.inet_aton(dest_addr)
- req += b"\x01" + addr_bytes
- except socket.error:
- # Well it's not an IP number, so it's probably a DNS name.
- if rdns:
- # Resolve remotely
- addr_bytes = None
- req += b"\x03" + chr(len(dest_addr)).encode() + dest_addr.encode()
- else:
- # Resolve locally
- addr_bytes = socket.inet_aton(socket.gethostbyname(dest_addr))
- req += b"\x01" + addr_bytes
-
- req += struct.pack(">H", dest_port)
- self.sendall(req)
-
- # Get the response
- resp = self._recvall(4)
- if resp[0:1] != b"\x05":
- raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
-
- status = ord(resp[1:2])
- if status != 0x00:
- # Connection failed: server returned an error
- error = SOCKS5_ERRORS.get(status, "Unknown error")
- raise SOCKS5Error("{:#04x}: {}".format(status, error))
-
- # Get the bound address/port
- if resp[3:4] == b"\x01":
- bound_addr = self._recvall(4)
- elif resp[3:4] == b"\x03":
- resp += self.recv(1)
- bound_addr = self._recvall(ord(resp[4:5]))
- else:
- raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
-
- bound_port = struct.unpack(">H", self._recvall(2))[0]
- self.proxy_sockname = bound_addr, bound_port
- if addr_bytes:
- self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port
- else:
- self.proxy_peername = dest_addr, dest_port
-
- def _negotiate_SOCKS4(self, dest_addr, dest_port):
- """
- Negotiates a connection through a SOCKS4 server.
- """
- proxy_type, addr, port, rdns, username, password = self.proxy
-
- # Check if the destination address provided is an IP address
- remote_resolve = False
- try:
- addr_bytes = socket.inet_aton(dest_addr)
- except socket.error:
- # It's a DNS name. Check where it should be resolved.
- if rdns:
- addr_bytes = b"\x00\x00\x00\x01"
- remote_resolve = True
- else:
- addr_bytes = socket.inet_aton(socket.gethostbyname(dest_addr))
-
- # Construct the request packet
- req = struct.pack(">BBH", 0x04, 0x01, dest_port) + addr_bytes
-
- # The username parameter is considered userid for SOCKS4
- if username:
- req += username
- req += b"\x00"
-
- # DNS name if remote resolving is required
- # NOTE: This is actually an extension to the SOCKS4 protocol
- # called SOCKS4A and may not be supported in all cases.
- if remote_resolve:
- req += dest_addr.encode() + b"\x00"
- self.sendall(req)
-
- # Get the response from the server
- resp = self._recvall(8)
- if resp[0:1] != b"\x00":
- # Bad data
- raise GeneralProxyError("SOCKS4 proxy server sent invalid data")
-
- status = ord(resp[1:2])
- if status != 0x5A:
- # Connection failed: server returned an error
- error = SOCKS4_ERRORS.get(status, "Unknown error")
- raise SOCKS4Error("{:#04x}: {}".format(status, error))
-
- # Get the bound address/port
- self.proxy_sockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
- if remote_resolve:
- self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port
- else:
- self.proxy_peername = dest_addr, dest_port
-
- def _negotiate_HTTP(self, dest_addr, dest_port):
- """
- Negotiates a connection through an HTTP server.
- NOTE: This currently only supports HTTP CONNECT-style proxies.
- """
- proxy_type, addr, port, rdns, username, password = self.proxy
-
- # If we need to resolve locally, we do this now
- addr = dest_addr if rdns else socket.gethostbyname(dest_addr)
-
- self.sendall(b"CONNECT " + addr.encode() + b":" + str(dest_port).encode() +
- b" HTTP/1.1\r\n" + b"Host: " + dest_addr.encode() + b"\r\n\r\n")
-
- # We just need the first line to check if the connection was successful
- fobj = self.makefile()
- status_line = fobj.readline()
- fobj.close()
-
- if not status_line:
- raise GeneralProxyError("Connection closed unexpectedly")
-
- try:
- proto, status_code, status_msg = status_line.split(" ", 2)
- except ValueError:
- raise GeneralProxyError("HTTP proxy server sent invalid response")
-
- if not proto.startswith("HTTP/"):
- raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy")
-
- try:
- status_code = int(status_code)
- except ValueError:
- raise HTTPError("HTTP proxy server did not return a valid HTTP status")
-
- if status_code != 200:
- error = "{}: {}".format(status_code, status_msg)
- if status_code in (400, 403, 405):
- # It's likely that the HTTP proxy server does not support the CONNECT tunneling method
- error += ("\n[*] Note: The HTTP proxy server may not be supported by PySocks"
- " (must be a CONNECT tunnel proxy)")
- raise HTTPError(error)
-
- self.proxy_sockname = (b"0.0.0.0", 0)
- self.proxy_peername = addr, dest_port
-
- def connect(self, dest_pair):
- """
- Connects to the specified destination through a proxy.
- Uses the same API as socket's connect().
- To select the proxy server, use set_proxy().
-
- dest_pair - 2-tuple of (IP/hostname, port).
- """
- proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
- dest_addr, dest_port = dest_pair
-
- # Do a minimal input check first
- if (not isinstance(dest_pair, (list, tuple))
- or len(dest_pair) != 2
- or not isinstance(dest_addr, type(""))
- or not isinstance(dest_port, int)):
- raise GeneralProxyError("Invalid destination-connection (host, port) pair")
-
- if proxy_type is None:
- # Treat like regular socket object
- _orig_socket.connect(self, (dest_addr, dest_port))
- return
-
- proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)
- if not proxy_port:
- raise GeneralProxyError("Invalid proxy type")
-
- try:
- # Initial connection to proxy server
- _orig_socket.connect(self, (proxy_addr, proxy_port))
-
- except socket.error as error:
- # Error while connecting to proxy
- self.close()
- proxy_server = "{}:{}".format(proxy_addr.decode(), proxy_port)
- printable_type = PRINTABLE_PROXY_TYPES[proxy_type]
-
- msg = "Error connecting to {} proxy {}".format(printable_type,
- proxy_server)
- raise ProxyConnectionError(msg, error)
-
- else:
- # Connected to proxy server, now negotiate
- try:
- # Calls negotiate_{SOCKS4, SOCKS5, HTTP}
- self.proxy_negotiators[proxy_type](dest_addr, dest_port)
- except socket.error as error:
- # Wrap socket errors
- self.close()
- raise GeneralProxyError("Socket error", error)
- except ProxyError:
- # Protocol error while negotiating with proxy
- self.close()
- raise
diff --git a/onionshare/strings.py b/onionshare/strings.py
index ccbfc37f..643186dd 100644
--- a/onionshare/strings.py
+++ b/onionshare/strings.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,39 +22,36 @@ import locale
import os
strings = {}
+translations = {}
-def load_strings(common, default="en"):
+def load_strings(common):
"""
Loads translated strings and fallback to English
if the translation does not exist.
"""
- global strings
+ global strings, translations
- # find locale dir
- locale_dir = common.get_resource_path('locale')
-
- # load all translations
+ # Load all translations
translations = {}
- for filename in os.listdir(locale_dir):
- abs_filename = os.path.join(locale_dir, filename)
- lang, ext = os.path.splitext(filename)
- if ext == '.json':
- with open(abs_filename, encoding='utf-8') as f:
- translations[lang] = json.load(f)
-
- strings = translations[default]
- lc, enc = locale.getdefaultlocale()
- if lc:
- lang = lc[:2]
- if lang in translations:
- # if a string doesn't exist, fallback to English
- for key in translations[default]:
- if key in translations[lang]:
- strings[key] = translations[lang][key]
-
-
-def translated(k, gui=False):
+ for locale in common.settings.available_locales:
+ locale_dir = common.get_resource_path('locale')
+ filename = os.path.join(locale_dir, "{}.json".format(locale))
+ with open(filename, encoding='utf-8') as f:
+ translations[locale] = json.load(f)
+
+ # Build strings
+ default_locale = 'en'
+ current_locale = common.settings.get('locale')
+ strings = {}
+ for s in translations[default_locale]:
+ if s in translations[current_locale] and translations[current_locale][s] != "":
+ strings[s] = translations[current_locale][s]
+ else:
+ strings[s] = translations[default_locale][s]
+
+
+def translated(k):
"""
Returns a translated string.
"""
diff --git a/onionshare/web.py b/onionshare/web.py
deleted file mode 100644
index e9c4d43b..00000000
--- a/onionshare/web.py
+++ /dev/null
@@ -1,454 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-OnionShare | https://onionshare.org/
-
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""
-
-import hmac
-import logging
-import mimetypes
-import os
-import queue
-import socket
-import sys
-import tempfile
-import base64
-from distutils.version import LooseVersion as Version
-from urllib.request import urlopen
-
-from flask import (
- Flask, Response, request, render_template_string, abort, make_response,
- __version__ as flask_version
-)
-
-from . import strings, common
-
-
-def _safe_select_jinja_autoescape(self, filename):
- if filename is None:
- return True
- return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
-
-# Starting in Flask 0.11, render_template_string autoescapes template variables
-# by default. To prevent content injection through template variables in
-# earlier versions of Flask, we force autoescaping in the Jinja2 template
-# engine if we detect a Flask version with insecure default behavior.
-if Version(flask_version) < Version('0.11'):
- # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
- Flask.select_jinja_autoescape = _safe_select_jinja_autoescape
-
-app = Flask(__name__)
-
-# information about the file
-file_info = []
-zip_filename = None
-zip_filesize = None
-
-security_headers = [
- ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'),
- ('X-Frame-Options', 'DENY'),
- ('X-Xss-Protection', '1; mode=block'),
- ('X-Content-Type-Options', 'nosniff'),
- ('Referrer-Policy', 'no-referrer'),
- ('Server', 'OnionShare')
-]
-
-
-def set_file_info(filenames, processed_size_callback=None):
- """
- Using the list of filenames being shared, fill in details that the web
- page will need to display. This includes zipping up the file in order to
- get the zip file's name and size.
- """
- global file_info, zip_filename, zip_filesize
-
- # build file info list
- file_info = {'files': [], 'dirs': []}
- for filename in filenames:
- info = {
- 'filename': filename,
- 'basename': os.path.basename(filename.rstrip('/'))
- }
- if os.path.isfile(filename):
- info['size'] = os.path.getsize(filename)
- info['size_human'] = common.human_readable_filesize(info['size'])
- file_info['files'].append(info)
- if os.path.isdir(filename):
- info['size'] = common.dir_size(filename)
- info['size_human'] = common.human_readable_filesize(info['size'])
- file_info['dirs'].append(info)
- file_info['files'] = sorted(file_info['files'], key=lambda k: k['basename'])
- file_info['dirs'] = sorted(file_info['dirs'], key=lambda k: k['basename'])
-
- # zip up the files and folders
- z = common.ZipWriter(processed_size_callback=processed_size_callback)
- for info in file_info['files']:
- z.add_file(info['filename'])
- for info in file_info['dirs']:
- z.add_dir(info['filename'])
- z.close()
- zip_filename = z.zip_filename
- zip_filesize = os.path.getsize(zip_filename)
-
-
-REQUEST_LOAD = 0
-REQUEST_DOWNLOAD = 1
-REQUEST_PROGRESS = 2
-REQUEST_OTHER = 3
-REQUEST_CANCELED = 4
-REQUEST_RATE_LIMIT = 5
-q = queue.Queue()
-
-
-def add_request(request_type, path, data=None):
- """
- Add a request to the queue, to communicate with the GUI.
- """
- global q
- q.put({
- 'type': request_type,
- 'path': path,
- 'data': data
- })
-
-
-# Load and base64 encode images to pass into templates
-favicon_b64 = base64.b64encode(open(common.get_resource_path('images/favicon.ico'), 'rb').read()).decode()
-logo_b64 = base64.b64encode(open(common.get_resource_path('images/logo.png'), 'rb').read()).decode()
-folder_b64 = base64.b64encode(open(common.get_resource_path('images/web_folder.png'), 'rb').read()).decode()
-file_b64 = base64.b64encode(open(common.get_resource_path('images/web_file.png'), 'rb').read()).decode()
-
-slug = None
-
-
-def generate_slug(persistent_slug=''):
- global slug
- if persistent_slug:
- slug = persistent_slug
- else:
- slug = common.build_slug()
-
-download_count = 0
-error404_count = 0
-
-stay_open = False
-
-
-def set_stay_open(new_stay_open):
- """
- Set stay_open variable.
- """
- global stay_open
- stay_open = new_stay_open
-
-
-def get_stay_open():
- """
- Get stay_open variable.
- """
- return stay_open
-
-
-# Are we running in GUI mode?
-gui_mode = False
-
-
-def set_gui_mode():
- """
- Tell the web service that we're running in GUI mode
- """
- global gui_mode
- gui_mode = True
-
-
-def debug_mode():
- """
- Turn on debugging mode, which will log flask errors to a debug file.
-
- This is commented out (it's only needed for debugging, and not needed
- for OnionShare 1.3.2) as a hotfix to resolve this issue:
- https://github.com/micahflee/onionshare/issues/837
- """
- pass
- """
- temp_dir = tempfile.gettempdir()
- log_handler = logging.FileHandler(
- os.path.join(temp_dir, 'onionshare_server.log'))
- log_handler.setLevel(logging.WARNING)
- app.logger.addHandler(log_handler)
- """
-
-
-def check_slug_candidate(slug_candidate, slug_compare=None):
- if not slug_compare:
- slug_compare = slug
- if not hmac.compare_digest(slug_compare, slug_candidate):
- abort(404)
-
-
-# If "Stop After First Download" is checked (stay_open == False), only allow
-# one download at a time.
-download_in_progress = False
-
-done = False
-
-@app.route("/<slug_candidate>")
-def index(slug_candidate):
- """
- Render the template for the onionshare landing page.
- """
- check_slug_candidate(slug_candidate)
-
- add_request(REQUEST_LOAD, request.path)
-
- # Deny new downloads if "Stop After First Download" is checked and there is
- # currently a download
- global stay_open, download_in_progress
- deny_download = not stay_open and download_in_progress
- if deny_download:
- r = make_response(render_template_string(
- open(common.get_resource_path('html/denied.html')).read(),
- favicon_b64=favicon_b64
- ))
- for header, value in security_headers:
- r.headers.set(header, value)
- return r
-
- # If download is allowed to continue, serve download page
-
- r = make_response(render_template_string(
- open(common.get_resource_path('html/index.html')).read(),
- favicon_b64=favicon_b64,
- logo_b64=logo_b64,
- folder_b64=folder_b64,
- file_b64=file_b64,
- slug=slug,
- file_info=file_info,
- filename=os.path.basename(zip_filename),
- filesize=zip_filesize,
- filesize_human=common.human_readable_filesize(zip_filesize)))
- for header, value in security_headers:
- r.headers.set(header, value)
- return r
-
-
-# If the client closes the OnionShare window while a download is in progress,
-# it should immediately stop serving the file. The client_cancel global is
-# used to tell the download function that the client is canceling the download.
-client_cancel = False
-
-
-@app.route("/<slug_candidate>/download")
-def download(slug_candidate):
- """
- Download the zip file.
- """
- check_slug_candidate(slug_candidate)
-
- # Deny new downloads if "Stop After First Download" is checked and there is
- # currently a download
- global stay_open, download_in_progress, done
- deny_download = not stay_open and download_in_progress
- if deny_download:
- r = make_response(render_template_string(
- open(common.get_resource_path('html/denied.html')).read(),
- favicon_b64=favicon_b64
- ))
- for header,value in security_headers:
- r.headers.set(header, value)
- return r
-
- global download_count
-
- # each download has a unique id
- download_id = download_count
- download_count += 1
-
- # prepare some variables to use inside generate() function below
- # which is outside of the request context
- shutdown_func = request.environ.get('werkzeug.server.shutdown')
- path = request.path
-
- # tell GUI the download started
- add_request(REQUEST_DOWNLOAD, path, {'id': download_id})
-
- dirname = os.path.dirname(zip_filename)
- basename = os.path.basename(zip_filename)
-
- def generate():
- # The user hasn't canceled the download
- global client_cancel, gui_mode
- client_cancel = False
-
- # Starting a new download
- global stay_open, download_in_progress, done
- if not stay_open:
- download_in_progress = True
-
- chunk_size = 102400 # 100kb
-
- fp = open(zip_filename, 'rb')
- done = False
- canceled = False
- while not done:
- # The user has canceled the download, so stop serving the file
- if client_cancel:
- add_request(REQUEST_CANCELED, path, {'id': download_id})
- break
-
- chunk = fp.read(chunk_size)
- if chunk == b'':
- done = True
- else:
- try:
- yield chunk
-
- # tell GUI the progress
- downloaded_bytes = fp.tell()
- percent = (1.0 * downloaded_bytes / zip_filesize) * 100
-
- # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
- plat = common.get_platform()
- if not gui_mode or plat == 'Linux' or plat == 'BSD':
- sys.stdout.write(
- "\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent))
- sys.stdout.flush()
-
- add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
- done = False
- except:
- # looks like the download was canceled
- done = True
- canceled = True
-
- # tell the GUI the download has canceled
- add_request(REQUEST_CANCELED, path, {'id': download_id})
-
- fp.close()
-
- if common.get_platform() != 'Darwin':
- sys.stdout.write("\n")
-
- # Download is finished
- if not stay_open:
- download_in_progress = False
-
- # Close the server, if necessary
- if not stay_open and not canceled:
- print(strings._("closing_automatically"))
- if shutdown_func is None:
- raise RuntimeError('Not running with the Werkzeug Server')
- shutdown_func()
-
- r = Response(generate())
- r.headers.set('Content-Length', zip_filesize)
- r.headers.set('Content-Disposition', 'attachment', filename=basename)
- for header,value in security_headers:
- r.headers.set(header, value)
- # guess content type
- (content_type, _) = mimetypes.guess_type(basename, strict=False)
- if content_type is not None:
- r.headers.set('Content-Type', content_type)
- return r
-
-
-@app.errorhandler(404)
-def page_not_found(e):
- """
- 404 error page.
- """
- add_request(REQUEST_OTHER, request.path)
-
- global error404_count
- if request.path != '/favicon.ico':
- error404_count += 1
- if error404_count == 20:
- add_request(REQUEST_RATE_LIMIT, request.path)
- force_shutdown()
- print(strings._('error_rate_limit'))
-
- r = make_response(render_template_string(
- open(common.get_resource_path('html/404.html')).read(),
- favicon_b64=favicon_b64
- ), 404)
- for header, value in security_headers:
- r.headers.set(header, value)
- return r
-
-
-# shutting down the server only works within the context of flask, so the easiest way to do it is over http
-shutdown_slug = common.random_string(16)
-
-
-@app.route("/<slug_candidate>/shutdown")
-def shutdown(slug_candidate):
- """
- Stop the flask web server, from the context of an http request.
- """
- check_slug_candidate(slug_candidate, shutdown_slug)
- force_shutdown()
- return ""
-
-
-def force_shutdown():
- """
- Stop the flask web server, from the context of the flask app.
- """
- # shutdown the flask service
- func = request.environ.get('werkzeug.server.shutdown')
- if func is None:
- raise RuntimeError('Not running with the Werkzeug Server')
- func()
-
-
-def start(port, stay_open=False, persistent_slug=''):
- """
- Start the flask web server.
- """
- generate_slug(persistent_slug)
-
- set_stay_open(stay_open)
-
- # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
- if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
- host = '0.0.0.0'
- else:
- host = '127.0.0.1'
-
- app.run(host=host, port=port, threaded=True)
-
-
-def stop(port):
- """
- Stop the flask web server by loading /shutdown.
- """
-
- # If the user cancels the download, let the download function know to stop
- # serving the file
- global client_cancel
- client_cancel = True
-
- # to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
- try:
- s = socket.socket()
- s.connect(('127.0.0.1', port))
- s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))
- except:
- try:
- urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read()
- except:
- pass
diff --git a/onionshare_gui/alert.py b/onionshare/web/__init__.py
index 75975cfb..d45b4983 100644
--- a/onionshare_gui/alert.py
+++ b/onionshare/web/__init__.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,23 +17,5 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-from PyQt5 import QtCore, QtWidgets, QtGui
-from onionshare import common
-
-class Alert(QtWidgets.QMessageBox):
- """
- An alert box dialog.
- """
- def __init__(self, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
- super(Alert, self).__init__(None)
- common.log('Alert', '__init__')
-
- self.setWindowTitle("OnionShare")
- self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
- self.setText(message)
- self.setIcon(icon)
- self.setStandardButtons(buttons)
-
- if autostart:
- self.exec_()
+from .web import Web
diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py
new file mode 100644
index 00000000..d6ef86ad
--- /dev/null
+++ b/onionshare/web/receive_mode.py
@@ -0,0 +1,442 @@
+import os
+import tempfile
+import json
+from datetime import datetime
+from flask import Request, request, render_template, make_response, flash, redirect
+from werkzeug.utils import secure_filename
+
+from .. import strings
+
+
+class ReceiveModeWeb(object):
+ """
+ All of the web logic for receive mode
+ """
+ def __init__(self, common, web):
+ self.common = common
+ self.common.log('ReceiveModeWeb', '__init__')
+
+ self.web = web
+
+ self.can_upload = True
+ self.upload_count = 0
+ self.uploads_in_progress = []
+
+ self.define_routes()
+
+ def define_routes(self):
+ """
+ The web app routes for receiving files
+ """
+ def index_logic():
+ self.web.add_request(self.web.REQUEST_LOAD, request.path)
+
+ if self.common.settings.get('public_mode'):
+ upload_action = '/upload'
+ else:
+ upload_action = '/{}/upload'.format(self.web.slug)
+
+ r = make_response(render_template(
+ 'receive.html',
+ upload_action=upload_action))
+ return self.web.add_security_headers(r)
+
+ @self.web.app.route("/<slug_candidate>")
+ def index(slug_candidate):
+ if not self.can_upload:
+ return self.web.error403()
+ self.web.check_slug_candidate(slug_candidate)
+ return index_logic()
+
+ @self.web.app.route("/")
+ def index_public():
+ if not self.can_upload:
+ return self.web.error403()
+ if not self.common.settings.get('public_mode'):
+ return self.web.error404()
+ return index_logic()
+
+
+ def upload_logic(slug_candidate='', ajax=False):
+ """
+ Handle the upload files POST request, though at this point, the files have
+ already been uploaded and saved to their correct locations.
+ """
+ files = request.files.getlist('file[]')
+ filenames = []
+ for f in files:
+ if f.filename != '':
+ filename = secure_filename(f.filename)
+ filenames.append(filename)
+ local_path = os.path.join(request.receive_mode_dir, filename)
+ basename = os.path.basename(local_path)
+
+ # Tell the GUI the receive mode directory for this file
+ self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, {
+ 'id': request.upload_id,
+ 'filename': basename,
+ 'dir': request.receive_mode_dir
+ })
+
+ self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path))
+ print('\n' + strings._('receive_mode_received_file').format(local_path))
+
+ if request.upload_error:
+ self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error')
+
+ self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, {
+ "receive_mode_dir": request.receive_mode_dir
+ })
+ print(strings._('error_cannot_create_data_dir').format(request.receive_mode_dir))
+
+ msg = 'Error uploading, please inform the OnionShare user'
+ if ajax:
+ return json.dumps({"error_flashes": [msg]})
+ else:
+ flash(msg, 'error')
+
+ if self.common.settings.get('public_mode'):
+ return redirect('/')
+ else:
+ return redirect('/{}'.format(slug_candidate))
+
+ # Note that flash strings are in English, and not translated, on purpose,
+ # to avoid leaking the locale of the OnionShare user
+ if ajax:
+ info_flashes = []
+
+ if len(filenames) == 0:
+ msg = 'No files uploaded'
+ if ajax:
+ info_flashes.append(msg)
+ else:
+ flash(msg, 'info')
+ else:
+ for filename in filenames:
+ msg = 'Sent {}'.format(filename)
+ if ajax:
+ info_flashes.append(msg)
+ else:
+ flash(msg, 'info')
+
+ if self.can_upload:
+ if ajax:
+ return json.dumps({"info_flashes": info_flashes})
+ else:
+ if self.common.settings.get('public_mode'):
+ path = '/'
+ else:
+ path = '/{}'.format(slug_candidate)
+ return redirect('{}'.format(path))
+ else:
+ if ajax:
+ return json.dumps({"new_body": render_template('thankyou.html')})
+ else:
+ # It was the last upload and the timer ran out
+ r = make_response(render_template('thankyou.html'))
+ return self.web.add_security_headers(r)
+
+ @self.web.app.route("/<slug_candidate>/upload", methods=['POST'])
+ def upload(slug_candidate):
+ if not self.can_upload:
+ return self.web.error403()
+ self.web.check_slug_candidate(slug_candidate)
+ return upload_logic(slug_candidate)
+
+ @self.web.app.route("/upload", methods=['POST'])
+ def upload_public():
+ if not self.can_upload:
+ return self.web.error403()
+ if not self.common.settings.get('public_mode'):
+ return self.web.error404()
+ return upload_logic()
+
+ @self.web.app.route("/<slug_candidate>/upload-ajax", methods=['POST'])
+ def upload_ajax(slug_candidate):
+ if not self.can_upload:
+ return self.web.error403()
+ self.web.check_slug_candidate(slug_candidate)
+ return upload_logic(slug_candidate, ajax=True)
+
+ @self.web.app.route("/upload-ajax", methods=['POST'])
+ def upload_ajax_public():
+ if not self.can_upload:
+ return self.web.error403()
+ if not self.common.settings.get('public_mode'):
+ return self.web.error404()
+ return upload_logic(ajax=True)
+
+
+class ReceiveModeWSGIMiddleware(object):
+ """
+ Custom WSGI middleware in order to attach the Web object to environ, so
+ ReceiveModeRequest can access it.
+ """
+ def __init__(self, app, web):
+ self.app = app
+ self.web = web
+
+ def __call__(self, environ, start_response):
+ environ['web'] = self.web
+ environ['stop_q'] = self.web.stop_q
+ return self.app(environ, start_response)
+
+
+class ReceiveModeFile(object):
+ """
+ A custom file object that tells ReceiveModeRequest every time data gets
+ written to it, in order to track the progress of uploads. It starts out with
+ a .part file extension, and when it's complete it removes that extension.
+ """
+ def __init__(self, request, filename, write_func, close_func):
+ self.onionshare_request = request
+ self.onionshare_filename = filename
+ self.onionshare_write_func = write_func
+ self.onionshare_close_func = close_func
+
+ self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename)
+ self.filename_in_progress = '{}.part'.format(self.filename)
+
+ # Open the file
+ self.upload_error = False
+ try:
+ self.f = open(self.filename_in_progress, 'wb+')
+ except:
+ # This will only happen if someone is messing with the data dir while
+ # OnionShare is running, but if it does make sure to throw an error
+ self.upload_error = True
+ self.f = tempfile.TemporaryFile('wb+')
+
+ # Make all the file-like methods and attributes actually access the
+ # TemporaryFile, except for write
+ attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
+ 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
+ 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
+ 'truncate', 'writable', 'writelines']
+ for attr in attrs:
+ setattr(self, attr, getattr(self.f, attr))
+
+ def write(self, b):
+ """
+ Custom write method that calls out to onionshare_write_func
+ """
+ if self.upload_error or (not self.onionshare_request.stop_q.empty()):
+ self.close()
+ self.onionshare_request.close()
+ return
+
+ try:
+ bytes_written = self.f.write(b)
+ self.onionshare_write_func(self.onionshare_filename, bytes_written)
+
+ except:
+ self.upload_error = True
+
+ def close(self):
+ """
+ Custom close method that calls out to onionshare_close_func
+ """
+ try:
+ self.f.close()
+
+ if not self.upload_error:
+ # Rename the in progress file to the final filename
+ os.rename(self.filename_in_progress, self.filename)
+
+ except:
+ self.upload_error = True
+
+ self.onionshare_close_func(self.onionshare_filename, self.upload_error)
+
+
+class ReceiveModeRequest(Request):
+ """
+ A custom flask Request object that keeps track of how much data has been
+ uploaded for each file, for receive mode.
+ """
+ def __init__(self, environ, populate_request=True, shallow=False):
+ super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
+ self.web = environ['web']
+ self.stop_q = environ['stop_q']
+
+ self.web.common.log('ReceiveModeRequest', '__init__')
+
+ # Prevent running the close() method more than once
+ self.closed = False
+
+ # Is this a valid upload request?
+ self.upload_request = False
+ if self.method == 'POST':
+ if self.web.common.settings.get('public_mode'):
+ if self.path == '/upload' or self.path == '/upload-ajax':
+ self.upload_request = True
+ else:
+ if self.path == '/{}/upload'.format(self.web.slug) or self.path == '/{}/upload-ajax'.format(self.web.slug):
+ self.upload_request = True
+
+ if self.upload_request:
+ # No errors yet
+ self.upload_error = False
+
+ # Figure out what files should be saved
+ now = datetime.now()
+ date_dir = now.strftime("%Y-%m-%d")
+ time_dir = now.strftime("%H.%M.%S")
+ self.receive_mode_dir = os.path.join(self.web.common.settings.get('data_dir'), date_dir, time_dir)
+
+ # Create that directory, which shouldn't exist yet
+ try:
+ os.makedirs(self.receive_mode_dir, 0o700, exist_ok=False)
+ except OSError:
+ # If this directory already exists, maybe someone else is uploading files at
+ # the same second, so use a different name in that case
+ if os.path.exists(self.receive_mode_dir):
+ # Keep going until we find a directory name that's available
+ i = 1
+ while True:
+ new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i)
+ try:
+ os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False)
+ break
+ except OSError:
+ pass
+ i += 1
+ # Failsafe
+ if i == 100:
+ self.web.common.log('ReceiveModeRequest', '__init__', 'Error finding available receive mode directory')
+ self.upload_error = True
+ break
+ except PermissionError:
+ self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, {
+ "receive_mode_dir": self.receive_mode_dir
+ })
+ print(strings._('error_cannot_create_data_dir').format(self.receive_mode_dir))
+ self.web.common.log('ReceiveModeRequest', '__init__', 'Permission denied creating receive mode directory')
+ self.upload_error = True
+
+ # If there's an error so far, finish early
+ if self.upload_error:
+ return
+
+ # A dictionary that maps filenames to the bytes uploaded so far
+ self.progress = {}
+
+ # Prevent new uploads if we've said so (timer expired)
+ if self.web.receive_mode.can_upload:
+
+ # Create an upload_id, attach it to the request
+ self.upload_id = self.web.receive_mode.upload_count
+
+ self.web.receive_mode.upload_count += 1
+
+ # Figure out the content length
+ try:
+ self.content_length = int(self.headers['Content-Length'])
+ except:
+ self.content_length = 0
+
+ print("{}: {}".format(
+ datetime.now().strftime("%b %d, %I:%M%p"),
+ strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length))
+ ))
+
+ # Don't tell the GUI that a request has started until we start receiving files
+ self.told_gui_about_request = False
+
+ self.previous_file = None
+
+ def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None):
+ """
+ This gets called for each file that gets uploaded, and returns an file-like
+ writable stream.
+ """
+ if self.upload_request:
+ if not self.told_gui_about_request:
+ # Tell the GUI about the request
+ self.web.add_request(self.web.REQUEST_STARTED, self.path, {
+ 'id': self.upload_id,
+ 'content_length': self.content_length
+ })
+ self.web.receive_mode.uploads_in_progress.append(self.upload_id)
+
+ self.told_gui_about_request = True
+
+ self.filename = secure_filename(filename)
+
+ self.progress[self.filename] = {
+ 'uploaded_bytes': 0,
+ 'complete': False
+ }
+
+ f = ReceiveModeFile(self, self.filename, self.file_write_func, self.file_close_func)
+ if f.upload_error:
+ self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file')
+ self.upload_error = True
+ return f
+
+ def close(self):
+ """
+ Closing the request.
+ """
+ super(ReceiveModeRequest, self).close()
+
+ # Prevent calling this method more than once per request
+ if self.closed:
+ return
+ self.closed = True
+
+ self.web.common.log('ReceiveModeRequest', 'close')
+
+ try:
+ if self.told_gui_about_request:
+ upload_id = self.upload_id
+
+ if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']:
+ # Inform the GUI that the upload has canceled
+ self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, {
+ 'id': upload_id
+ })
+ else:
+ # Inform the GUI that the upload has finished
+ self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, {
+ 'id': upload_id
+ })
+ self.web.receive_mode.uploads_in_progress.remove(upload_id)
+
+ except AttributeError:
+ pass
+
+ def file_write_func(self, filename, length):
+ """
+ This function gets called when a specific file is written to.
+ """
+ if self.closed:
+ return
+
+ if self.upload_request:
+ self.progress[filename]['uploaded_bytes'] += length
+
+ if self.previous_file != filename:
+ self.previous_file = filename
+
+ print('\r=> {:15s} {}'.format(
+ self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']),
+ filename
+ ), end='')
+
+ # Update the GUI on the upload progress
+ if self.told_gui_about_request:
+ self.web.add_request(self.web.REQUEST_PROGRESS, self.path, {
+ 'id': self.upload_id,
+ 'progress': self.progress
+ })
+
+ def file_close_func(self, filename, upload_error=False):
+ """
+ This function gets called when a specific file is closed.
+ """
+ self.progress[filename]['complete'] = True
+
+ # If the file tells us there was an upload error, let the request know as well
+ if upload_error:
+ self.upload_error = True
diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py
new file mode 100644
index 00000000..eb487c42
--- /dev/null
+++ b/onionshare/web/share_mode.py
@@ -0,0 +1,376 @@
+import os
+import sys
+import tempfile
+import zipfile
+import mimetypes
+import gzip
+from flask import Response, request, render_template, make_response
+
+from .. import strings
+
+
+class ShareModeWeb(object):
+ """
+ All of the web logic for share mode
+ """
+ def __init__(self, common, web):
+ self.common = common
+ self.common.log('ShareModeWeb', '__init__')
+
+ self.web = web
+
+ # Information about the file to be shared
+ self.file_info = []
+ self.is_zipped = False
+ self.download_filename = None
+ self.download_filesize = None
+ self.gzip_filename = None
+ self.gzip_filesize = None
+ self.zip_writer = None
+
+ self.download_count = 0
+
+ # If "Stop After First Download" is checked (stay_open == False), only allow
+ # one download at a time.
+ self.download_in_progress = False
+
+ self.define_routes()
+
+ def define_routes(self):
+ """
+ The web app routes for sharing files
+ """
+ @self.web.app.route("/<slug_candidate>")
+ def index(slug_candidate):
+ self.web.check_slug_candidate(slug_candidate)
+ return index_logic()
+
+ @self.web.app.route("/")
+ def index_public():
+ if not self.common.settings.get('public_mode'):
+ return self.web.error404()
+ return index_logic()
+
+ def index_logic(slug_candidate=''):
+ """
+ Render the template for the onionshare landing page.
+ """
+ self.web.add_request(self.web.REQUEST_LOAD, request.path)
+
+ # Deny new downloads if "Stop After First Download" is checked and there is
+ # currently a download
+ deny_download = not self.web.stay_open and self.download_in_progress
+ if deny_download:
+ r = make_response(render_template('denied.html'))
+ return self.web.add_security_headers(r)
+
+ # If download is allowed to continue, serve download page
+ if self.should_use_gzip():
+ self.filesize = self.gzip_filesize
+ else:
+ self.filesize = self.download_filesize
+
+ if self.web.slug:
+ r = make_response(render_template(
+ 'send.html',
+ slug=self.web.slug,
+ file_info=self.file_info,
+ filename=os.path.basename(self.download_filename),
+ filesize=self.filesize,
+ filesize_human=self.common.human_readable_filesize(self.download_filesize),
+ is_zipped=self.is_zipped))
+ else:
+ # If download is allowed to continue, serve download page
+ r = make_response(render_template(
+ 'send.html',
+ file_info=self.file_info,
+ filename=os.path.basename(self.download_filename),
+ filesize=self.filesize,
+ filesize_human=self.common.human_readable_filesize(self.download_filesize),
+ is_zipped=self.is_zipped))
+ return self.web.add_security_headers(r)
+
+ @self.web.app.route("/<slug_candidate>/download")
+ def download(slug_candidate):
+ self.web.check_slug_candidate(slug_candidate)
+ return download_logic()
+
+ @self.web.app.route("/download")
+ def download_public():
+ if not self.common.settings.get('public_mode'):
+ return self.web.error404()
+ return download_logic()
+
+ def download_logic(slug_candidate=''):
+ """
+ Download the zip file.
+ """
+ # Deny new downloads if "Stop After First Download" is checked and there is
+ # currently a download
+ deny_download = not self.web.stay_open and self.download_in_progress
+ if deny_download:
+ r = make_response(render_template('denied.html'))
+ return self.web.add_security_headers(r)
+
+ # Each download has a unique id
+ download_id = self.download_count
+ self.download_count += 1
+
+ # Prepare some variables to use inside generate() function below
+ # which is outside of the request context
+ shutdown_func = request.environ.get('werkzeug.server.shutdown')
+ path = request.path
+
+ # If this is a zipped file, then serve as-is. If it's not zipped, then,
+ # if the http client supports gzip compression, gzip the file first
+ # and serve that
+ use_gzip = self.should_use_gzip()
+ if use_gzip:
+ file_to_download = self.gzip_filename
+ self.filesize = self.gzip_filesize
+ else:
+ file_to_download = self.download_filename
+ self.filesize = self.download_filesize
+
+ # Tell GUI the download started
+ self.web.add_request(self.web.REQUEST_STARTED, path, {
+ 'id': download_id,
+ 'use_gzip': use_gzip
+ })
+
+ basename = os.path.basename(self.download_filename)
+
+ def generate():
+ # Starting a new download
+ if not self.web.stay_open:
+ self.download_in_progress = True
+
+ chunk_size = 102400 # 100kb
+
+ fp = open(file_to_download, 'rb')
+ self.web.done = False
+ canceled = False
+ while not self.web.done:
+ # The user has canceled the download, so stop serving the file
+ if not self.web.stop_q.empty():
+ self.web.add_request(self.web.REQUEST_CANCELED, path, {
+ 'id': download_id
+ })
+ break
+
+ chunk = fp.read(chunk_size)
+ if chunk == b'':
+ self.web.done = True
+ else:
+ try:
+ yield chunk
+
+ # tell GUI the progress
+ downloaded_bytes = fp.tell()
+ percent = (1.0 * downloaded_bytes / self.filesize) * 100
+
+ # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
+ if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD':
+ sys.stdout.write(
+ "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
+ sys.stdout.flush()
+
+ self.web.add_request(self.web.REQUEST_PROGRESS, path, {
+ 'id': download_id,
+ 'bytes': downloaded_bytes
+ })
+ self.web.done = False
+ except:
+ # looks like the download was canceled
+ self.web.done = True
+ canceled = True
+
+ # tell the GUI the download has canceled
+ self.web.add_request(self.web.REQUEST_CANCELED, path, {
+ 'id': download_id
+ })
+
+ fp.close()
+
+ if self.common.platform != 'Darwin':
+ sys.stdout.write("\n")
+
+ # Download is finished
+ if not self.web.stay_open:
+ self.download_in_progress = False
+
+ # Close the server, if necessary
+ if not self.web.stay_open and not canceled:
+ print(strings._("closing_automatically"))
+ self.web.running = False
+ try:
+ if shutdown_func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ shutdown_func()
+ except:
+ pass
+
+ r = Response(generate())
+ if use_gzip:
+ r.headers.set('Content-Encoding', 'gzip')
+ r.headers.set('Content-Length', self.filesize)
+ r.headers.set('Content-Disposition', 'attachment', filename=basename)
+ r = self.web.add_security_headers(r)
+ # guess content type
+ (content_type, _) = mimetypes.guess_type(basename, strict=False)
+ if content_type is not None:
+ r.headers.set('Content-Type', content_type)
+ return r
+
+ def set_file_info(self, filenames, processed_size_callback=None):
+ """
+ Using the list of filenames being shared, fill in details that the web
+ page will need to display. This includes zipping up the file in order to
+ get the zip file's name and size.
+ """
+ self.common.log("ShareModeWeb", "set_file_info")
+ self.web.cancel_compression = False
+
+ self.cleanup_filenames = []
+
+ # build file info list
+ self.file_info = {'files': [], 'dirs': []}
+ for filename in filenames:
+ info = {
+ 'filename': filename,
+ 'basename': os.path.basename(filename.rstrip('/'))
+ }
+ if os.path.isfile(filename):
+ info['size'] = os.path.getsize(filename)
+ info['size_human'] = self.common.human_readable_filesize(info['size'])
+ self.file_info['files'].append(info)
+ if os.path.isdir(filename):
+ info['size'] = self.common.dir_size(filename)
+ info['size_human'] = self.common.human_readable_filesize(info['size'])
+ self.file_info['dirs'].append(info)
+ self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
+ self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
+
+ # Check if there's only 1 file and no folders
+ if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0:
+ self.download_filename = self.file_info['files'][0]['filename']
+ self.download_filesize = self.file_info['files'][0]['size']
+
+ # Compress the file with gzip now, so we don't have to do it on each request
+ self.gzip_filename = tempfile.mkstemp('wb+')[1]
+ self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback)
+ self.gzip_filesize = os.path.getsize(self.gzip_filename)
+
+ # Make sure the gzip file gets cleaned up when onionshare stops
+ self.cleanup_filenames.append(self.gzip_filename)
+
+ self.is_zipped = False
+
+ else:
+ # Zip up the files and folders
+ self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback)
+ self.download_filename = self.zip_writer.zip_filename
+ for info in self.file_info['files']:
+ self.zip_writer.add_file(info['filename'])
+ # Canceling early?
+ if self.web.cancel_compression:
+ self.zip_writer.close()
+ return False
+
+ for info in self.file_info['dirs']:
+ if not self.zip_writer.add_dir(info['filename']):
+ return False
+
+ self.zip_writer.close()
+ self.download_filesize = os.path.getsize(self.download_filename)
+
+ # Make sure the zip file gets cleaned up when onionshare stops
+ self.cleanup_filenames.append(self.zip_writer.zip_filename)
+
+ self.is_zipped = True
+
+ return True
+
+ def should_use_gzip(self):
+ """
+ Should we use gzip for this browser?
+ """
+ return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower())
+
+ def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None):
+ """
+ Compress a file with gzip, without loading the whole thing into memory
+ Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror
+ """
+ bytes_processed = 0
+ blocksize = 1 << 16 # 64kB
+ with open(input_filename, 'rb') as input_file:
+ output_file = gzip.open(output_filename, 'wb', level)
+ while True:
+ if processed_size_callback is not None:
+ processed_size_callback(bytes_processed)
+
+ block = input_file.read(blocksize)
+ if len(block) == 0:
+ break
+ output_file.write(block)
+ bytes_processed += blocksize
+
+ output_file.close()
+
+
+class ZipWriter(object):
+ """
+ ZipWriter accepts files and directories and compresses them into a zip file
+ with. If a zip_filename is not passed in, it will use the default onionshare
+ filename.
+ """
+ def __init__(self, common, zip_filename=None, processed_size_callback=None):
+ self.common = common
+ self.cancel_compression = False
+
+ if zip_filename:
+ self.zip_filename = zip_filename
+ else:
+ self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6))
+
+ self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
+ self.processed_size_callback = processed_size_callback
+ if self.processed_size_callback is None:
+ self.processed_size_callback = lambda _: None
+ self._size = 0
+ self.processed_size_callback(self._size)
+
+ def add_file(self, filename):
+ """
+ Add a file to the zip archive.
+ """
+ self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
+ self._size += os.path.getsize(filename)
+ self.processed_size_callback(self._size)
+
+ def add_dir(self, filename):
+ """
+ Add a directory, and all of its children, to the zip archive.
+ """
+ dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
+ for dirpath, dirnames, filenames in os.walk(filename):
+ for f in filenames:
+ # Canceling early?
+ if self.cancel_compression:
+ return False
+
+ full_filename = os.path.join(dirpath, f)
+ if not os.path.islink(full_filename):
+ arc_filename = full_filename[len(dir_to_strip):]
+ self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
+ self._size += os.path.getsize(full_filename)
+ self.processed_size_callback(self._size)
+
+ return True
+
+ def close(self):
+ """
+ Close the zip archive.
+ """
+ self.z.close()
diff --git a/onionshare/web/web.py b/onionshare/web/web.py
new file mode 100644
index 00000000..010702be
--- /dev/null
+++ b/onionshare/web/web.py
@@ -0,0 +1,276 @@
+import hmac
+import logging
+import os
+import queue
+import socket
+import sys
+import tempfile
+from distutils.version import LooseVersion as Version
+from urllib.request import urlopen
+
+import flask
+from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version
+
+from .. import strings
+
+from .share_mode import ShareModeWeb
+from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
+
+
+# Stub out flask's show_server_banner function, to avoiding showing warnings that
+# are not applicable to OnionShare
+def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
+ pass
+
+flask.cli.show_server_banner = stubbed_show_server_banner
+
+
+class Web(object):
+ """
+ The Web object is the OnionShare web server, powered by flask
+ """
+ REQUEST_LOAD = 0
+ REQUEST_STARTED = 1
+ REQUEST_PROGRESS = 2
+ REQUEST_OTHER = 3
+ REQUEST_CANCELED = 4
+ REQUEST_RATE_LIMIT = 5
+ REQUEST_UPLOAD_FILE_RENAMED = 6
+ REQUEST_UPLOAD_SET_DIR = 7
+ REQUEST_UPLOAD_FINISHED = 8
+ REQUEST_UPLOAD_CANCELED = 9
+ REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10
+
+ def __init__(self, common, is_gui, mode='share'):
+ self.common = common
+ self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode))
+
+ # The flask app
+ self.app = Flask(__name__,
+ static_folder=self.common.get_resource_path('static'),
+ template_folder=self.common.get_resource_path('templates'))
+ self.app.secret_key = self.common.random_string(8)
+
+ # Debug mode?
+ if self.common.debug:
+ self.debug_mode()
+
+ # Are we running in GUI mode?
+ self.is_gui = is_gui
+
+ # If the user stops the server while a transfer is in progress, it should
+ # immediately stop the transfer. In order to make it thread-safe, stop_q
+ # is a queue. If anything is in it, then the user stopped the server
+ self.stop_q = queue.Queue()
+
+ # Are we using receive mode?
+ self.mode = mode
+ if self.mode == 'receive':
+ # Use custom WSGI middleware, to modify environ
+ self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
+ # Use a custom Request class to track upload progess
+ self.app.request_class = ReceiveModeRequest
+
+ # Starting in Flask 0.11, render_template_string autoescapes template variables
+ # by default. To prevent content injection through template variables in
+ # earlier versions of Flask, we force autoescaping in the Jinja2 template
+ # engine if we detect a Flask version with insecure default behavior.
+ if Version(flask_version) < Version('0.11'):
+ # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
+ Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
+
+ self.security_headers = [
+ ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
+ ('X-Frame-Options', 'DENY'),
+ ('X-Xss-Protection', '1; mode=block'),
+ ('X-Content-Type-Options', 'nosniff'),
+ ('Referrer-Policy', 'no-referrer'),
+ ('Server', 'OnionShare')
+ ]
+
+ self.q = queue.Queue()
+ self.slug = None
+ self.error404_count = 0
+
+ self.done = False
+
+ # shutting down the server only works within the context of flask, so the easiest way to do it is over http
+ self.shutdown_slug = self.common.random_string(16)
+
+ # Keep track if the server is running
+ self.running = False
+
+ # Define the web app routes
+ self.define_common_routes()
+
+ # Create the mode web object, which defines its own routes
+ self.share_mode = None
+ self.receive_mode = None
+ if self.mode == 'receive':
+ self.receive_mode = ReceiveModeWeb(self.common, self)
+ elif self.mode == 'share':
+ self.share_mode = ShareModeWeb(self.common, self)
+
+
+ def define_common_routes(self):
+ """
+ Common web app routes between sending and receiving
+ """
+ @self.app.errorhandler(404)
+ def page_not_found(e):
+ """
+ 404 error page.
+ """
+ return self.error404()
+
+ @self.app.route("/<slug_candidate>/shutdown")
+ def shutdown(slug_candidate):
+ """
+ Stop the flask web server, from the context of an http request.
+ """
+ self.check_shutdown_slug_candidate(slug_candidate)
+ self.force_shutdown()
+ return ""
+
+ @self.app.route("/noscript-xss-instructions")
+ def noscript_xss_instructions():
+ """
+ Display instructions for disabling Tor Browser's NoScript XSS setting
+ """
+ r = make_response(render_template('receive_noscript_xss.html'))
+ return self.add_security_headers(r)
+
+ def error404(self):
+ self.add_request(Web.REQUEST_OTHER, request.path)
+ if request.path != '/favicon.ico':
+ self.error404_count += 1
+
+ # In receive mode, with public mode enabled, skip rate limiting 404s
+ if not self.common.settings.get('public_mode'):
+ if self.error404_count == 20:
+ self.add_request(Web.REQUEST_RATE_LIMIT, request.path)
+ self.force_shutdown()
+ print(strings._('error_rate_limit'))
+
+ r = make_response(render_template('404.html'), 404)
+ return self.add_security_headers(r)
+
+ def error403(self):
+ self.add_request(Web.REQUEST_OTHER, request.path)
+
+ r = make_response(render_template('403.html'), 403)
+ return self.add_security_headers(r)
+
+ def add_security_headers(self, r):
+ """
+ Add security headers to a request
+ """
+ for header, value in self.security_headers:
+ r.headers.set(header, value)
+ return r
+
+ def _safe_select_jinja_autoescape(self, filename):
+ if filename is None:
+ return True
+ return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
+
+ def add_request(self, request_type, path, data=None):
+ """
+ Add a request to the queue, to communicate with the GUI.
+ """
+ self.q.put({
+ 'type': request_type,
+ 'path': path,
+ 'data': data
+ })
+
+ def generate_slug(self, persistent_slug=None):
+ self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug))
+ if persistent_slug != None and persistent_slug != '':
+ self.slug = persistent_slug
+ self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug))
+ else:
+ self.slug = self.common.build_slug()
+ self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug))
+
+ def debug_mode(self):
+ """
+ Turn on debugging mode, which will log flask errors to a debug file.
+ """
+ flask_debug_filename = os.path.join(self.common.build_data_dir(), 'flask_debug.log')
+ log_handler = logging.FileHandler(flask_debug_filename)
+ log_handler.setLevel(logging.WARNING)
+ self.app.logger.addHandler(log_handler)
+
+ def check_slug_candidate(self, slug_candidate):
+ self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate))
+ if self.common.settings.get('public_mode'):
+ abort(404)
+ if not hmac.compare_digest(self.slug, slug_candidate):
+ abort(404)
+
+ def check_shutdown_slug_candidate(self, slug_candidate):
+ self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate))
+ if not hmac.compare_digest(self.shutdown_slug, slug_candidate):
+ abort(404)
+
+ def force_shutdown(self):
+ """
+ Stop the flask web server, from the context of the flask app.
+ """
+ # Shutdown the flask service
+ try:
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+ except:
+ pass
+ self.running = False
+
+ def start(self, port, stay_open=False, public_mode=False, persistent_slug=None):
+ """
+ Start the flask web server.
+ """
+ self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug))
+ if not public_mode:
+ self.generate_slug(persistent_slug)
+
+ self.stay_open = stay_open
+
+ # Make sure the stop_q is empty when starting a new server
+ while not self.stop_q.empty():
+ try:
+ self.stop_q.get(block=False)
+ except queue.Empty:
+ pass
+
+ # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
+ if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
+ host = '0.0.0.0'
+ else:
+ host = '127.0.0.1'
+
+ self.running = True
+ self.app.run(host=host, port=port, threaded=True)
+
+ def stop(self, port):
+ """
+ Stop the flask web server by loading /shutdown.
+ """
+ self.common.log('Web', 'stop', 'stopping server')
+
+ # Let the mode know that the user stopped the server
+ self.stop_q.put(True)
+
+ # To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
+ if self.running:
+ try:
+ s = socket.socket()
+ s.connect(('127.0.0.1', port))
+ s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug))
+ except:
+ try:
+ urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read()
+ except:
+ pass
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py
index 5350fe29..675bb52d 100644
--- a/onionshare_gui/__init__.py
+++ b/onionshare_gui/__init__.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,14 +18,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import division
-import os, sys, platform, argparse
-from .alert import Alert
+import os
+import sys
+import platform
+import argparse
+import signal
+from .widgets import Alert
from PyQt5 import QtCore, QtWidgets
-from onionshare import strings, common, web
+from onionshare import strings
+from onionshare.common import Common
from onionshare.onion import Onion
from onionshare.onionshare import OnionShare
-from onionshare.settings import Settings
from .onionshare_gui import OnionShareGui
@@ -34,9 +38,8 @@ class Application(QtWidgets.QApplication):
This is Qt's QApplication class. It has been overridden to support threads
and the quick keyboard shortcut.
"""
- def __init__(self):
- system = common.get_platform()
- if system == 'Linux' or system == 'BSD':
+ def __init__(self, common):
+ if common.platform == 'Linux' or common.platform == 'BSD':
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtWidgets.QApplication.__init__(self, sys.argv)
self.installEventFilter(self)
@@ -53,18 +56,31 @@ def main():
"""
The main() function implements all of the logic that the GUI version of onionshare uses.
"""
+ common = Common()
+ common.define_css()
+
+ # Load the default settings and strings early, for the sake of being able to parse options.
+ # These won't be in the user's chosen locale necessarily, but we need to parse them
+ # early in order to even display the option to pass alternate settings (which might
+ # contain a preferred locale).
+ # If an alternate --config is passed, we'll reload strings later.
+ common.load_settings()
strings.load_strings(common)
- print(strings._('version_string').format(common.get_version()))
+
+ # Display OnionShare banner
+ print(strings._('version_string').format(common.version))
+
+ # Allow Ctrl-C to smoothly quit the program instead of throwing an exception
+ # https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
# Start the Qt app
global qtapp
- qtapp = Application()
+ qtapp = Application(common)
# Parse arguments
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48))
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
- parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
- parser.add_argument('--shutdown-timeout', metavar='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout"))
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename'))
parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config'))
@@ -76,39 +92,38 @@ def main():
filenames[i] = os.path.abspath(filenames[i])
config = args.config
+ if config:
+ # Re-load the strings, in case the provided config has changed locale
+ common.load_settings(config)
+ strings.load_strings(common)
local_only = bool(args.local_only)
- stay_open = bool(args.stay_open)
- shutdown_timeout = int(args.shutdown_timeout)
debug = bool(args.debug)
# Debug mode?
- if debug:
- common.set_debug(debug)
- web.debug_mode()
+ common.debug = debug
# Validation
if filenames:
valid = True
for filename in filenames:
if not os.path.isfile(filename) and not os.path.isdir(filename):
- Alert(strings._("not_a_file", True).format(filename))
+ Alert(common, strings._("not_a_file").format(filename))
valid = False
if not os.access(filename, os.R_OK):
- Alert(strings._("not_a_readable_file", True).format(filename))
+ Alert(common, strings._("not_a_readable_file").format(filename))
valid = False
if not valid:
sys.exit()
# Start the Onion
- onion = Onion()
+ onion = Onion(common)
# Start the OnionShare app
- web.set_stay_open(stay_open)
- app = OnionShare(onion, local_only, stay_open, shutdown_timeout)
+ app = OnionShare(common, onion, local_only)
# Launch the gui
- gui = OnionShareGui(onion, qtapp, app, filenames, config)
+ gui = OnionShareGui(common, onion, qtapp, app, filenames, config, local_only)
# Clean up when app quits
def shutdown():
diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py
deleted file mode 100644
index bf891b55..00000000
--- a/onionshare_gui/downloads.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-OnionShare | https://onionshare.org/
-
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""
-import time
-
-from PyQt5 import QtCore, QtWidgets
-
-from onionshare import strings, common
-
-class Download(object):
-
- def __init__(self, download_id, total_bytes):
- self.download_id = download_id
- self.started = time.time()
- self.total_bytes = total_bytes
- self.downloaded_bytes = 0
-
- # make a new progress bar
- cssStyleData ="""
- QProgressBar {
- border: 1px solid #4e064f;
- background-color: #ffffff !important;
- text-align: center;
- color: #9b9b9b;
- font-size: 12px;
- }
-
- QProgressBar::chunk {
- background-color: #4e064f;
- width: 10px;
- }"""
- self.progress_bar = QtWidgets.QProgressBar()
- self.progress_bar.setTextVisible(True)
- self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
- self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
- self.progress_bar.setMinimum(0)
- self.progress_bar.setMaximum(total_bytes)
- self.progress_bar.setValue(0)
- self.progress_bar.setStyleSheet(cssStyleData)
- self.progress_bar.total_bytes = total_bytes
-
- # start at 0
- self.update(0)
-
- def update(self, downloaded_bytes):
- self.downloaded_bytes = downloaded_bytes
-
- self.progress_bar.setValue(downloaded_bytes)
- if downloaded_bytes == self.progress_bar.total_bytes:
- pb_fmt = strings._('gui_download_progress_complete').format(
- common.format_seconds(time.time() - self.started))
- else:
- elapsed = time.time() - self.started
- if elapsed < 10:
- # Wait a couple of seconds for the download rate to stabilize.
- # This prevents a "Windows copy dialog"-esque experience at
- # the beginning of the download.
- pb_fmt = strings._('gui_download_progress_starting').format(
- common.human_readable_filesize(downloaded_bytes))
- else:
- pb_fmt = strings._('gui_download_progress_eta').format(
- common.human_readable_filesize(downloaded_bytes),
- self.estimated_time_remaining)
-
- self.progress_bar.setFormat(pb_fmt)
-
- def cancel(self):
- self.progress_bar.setFormat(strings._('gui_canceled'))
-
- @property
- def estimated_time_remaining(self):
- return common.estimated_time_remaining(self.downloaded_bytes,
- self.total_bytes,
- self.started)
-
-
-class Downloads(QtWidgets.QWidget):
- """
- The downloads chunk of the GUI. This lists all of the active download
- progress bars.
- """
- def __init__(self):
- super(Downloads, self).__init__()
- self.downloads = {}
- self.layout = QtWidgets.QVBoxLayout()
- self.setLayout(self.layout)
-
- def add_download(self, download_id, total_bytes):
- """
- Add a new download progress bar.
- """
- self.parent().show()
-
- # add it to the list
- download = Download(download_id, total_bytes)
- self.downloads[download_id] = download
- self.layout.insertWidget(-1, download.progress_bar)
-
- def update_download(self, download_id, downloaded_bytes):
- """
- Update the progress of a download progress bar.
- """
- self.downloads[download_id].update(downloaded_bytes)
-
- def cancel_download(self, download_id):
- """
- Update a download progress bar to show that it has been canceled.
- """
- self.downloads[download_id].cancel()
-
- def reset_downloads(self):
- """
- Reset the downloads back to zero
- """
- for download in self.downloads.values():
- self.layout.removeWidget(download.progress_bar)
- download.progress_bar.close()
- self.downloads = {}
diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py
new file mode 100644
index 00000000..4fe335e7
--- /dev/null
+++ b/onionshare_gui/mode/__init__.py
@@ -0,0 +1,337 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from onionshare import strings
+from onionshare.common import ShutdownTimer
+
+from ..server_status import ServerStatus
+from ..threads import OnionThread
+from ..widgets import Alert
+
+class Mode(QtWidgets.QWidget):
+ """
+ The class that ShareMode and ReceiveMode inherit from.
+ """
+ start_server_finished = QtCore.pyqtSignal()
+ stop_server_finished = QtCore.pyqtSignal()
+ starting_server_step2 = QtCore.pyqtSignal()
+ starting_server_step3 = QtCore.pyqtSignal()
+ starting_server_error = QtCore.pyqtSignal(str)
+ set_server_active = QtCore.pyqtSignal(bool)
+
+ def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False):
+ super(Mode, self).__init__()
+ self.common = common
+ self.qtapp = qtapp
+ self.app = app
+
+ self.status_bar = status_bar
+ self.server_status_label = server_status_label
+ self.system_tray = system_tray
+
+ self.filenames = filenames
+
+ # The web object gets created in init()
+ self.web = None
+
+ # Local mode is passed from OnionShareGui
+ self.local_only = local_only
+
+ # Threads start out as None
+ self.onion_thread = None
+ self.web_thread = None
+
+ # Server status
+ self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only)
+ self.server_status.server_started.connect(self.start_server)
+ self.server_status.server_stopped.connect(self.stop_server)
+ self.server_status.server_canceled.connect(self.cancel_server)
+ self.start_server_finished.connect(self.server_status.start_server_finished)
+ self.stop_server_finished.connect(self.server_status.stop_server_finished)
+ self.starting_server_step2.connect(self.start_server_step2)
+ self.starting_server_step3.connect(self.start_server_step3)
+ self.starting_server_error.connect(self.start_server_error)
+
+ # Primary action
+ # Note: It's up to the downstream Mode to add this to its layout
+ self.primary_action_layout = QtWidgets.QVBoxLayout()
+ self.primary_action_layout.addWidget(self.server_status)
+ self.primary_action = QtWidgets.QWidget()
+ self.primary_action.setLayout(self.primary_action_layout)
+
+ # Hack to allow a minimum width on the main layout
+ # Note: It's up to the downstream Mode to add this to its layout
+ self.min_width_widget = QtWidgets.QWidget()
+ self.min_width_widget.setMinimumWidth(600)
+
+ def init(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def timer_callback(self):
+ """
+ This method is called regularly on a timer.
+ """
+ # If the auto-shutdown timer has stopped, stop the server
+ if self.server_status.status == ServerStatus.STATUS_STARTED:
+ if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'):
+ if self.timeout > 0:
+ now = QtCore.QDateTime.currentDateTime()
+ seconds_remaining = now.secsTo(self.server_status.timeout)
+
+ # Update the server button
+ server_button_text = self.get_stop_server_shutdown_timeout_text()
+ self.server_status.server_button.setText(server_button_text.format(seconds_remaining))
+
+ self.status_bar.clearMessage()
+ if not self.app.shutdown_timer.is_alive():
+ if self.timeout_finished_should_stop_server():
+ self.server_status.stop_server()
+
+ def timer_callback_custom(self):
+ """
+ Add custom timer code.
+ """
+ pass
+
+ def get_stop_server_shutdown_timeout_text(self):
+ """
+ Return the string to put on the stop server button, if there's a shutdown timeout
+ """
+ pass
+
+ def timeout_finished_should_stop_server(self):
+ """
+ The shutdown timer expired, should we stop the server? Returns a bool
+ """
+ pass
+
+ def start_server(self):
+ """
+ Start the onionshare server. This uses multiple threads to start the Tor onion
+ server and the web app.
+ """
+ self.common.log('Mode', 'start_server')
+
+ self.start_server_custom()
+
+ self.set_server_active.emit(True)
+ self.app.set_stealth(self.common.settings.get('use_stealth'))
+
+ # Clear the status bar
+ self.status_bar.clearMessage()
+ self.server_status_label.setText('')
+
+ self.common.log('Mode', 'start_server', 'Starting an onion thread')
+ self.onion_thread = OnionThread(self)
+ self.onion_thread.success.connect(self.starting_server_step2.emit)
+ self.onion_thread.error.connect(self.starting_server_error.emit)
+ self.onion_thread.start()
+
+ def start_server_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def start_server_step2(self):
+ """
+ Step 2 in starting the onionshare server.
+ """
+ self.common.log('Mode', 'start_server_step2')
+
+ self.start_server_step2_custom()
+
+ # Nothing to do here.
+
+ # start_server_step2_custom has call these to move on:
+ # self.starting_server_step3.emit()
+ # self.start_server_finished.emit()
+
+ def start_server_step2_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def start_server_step3(self):
+ """
+ Step 3 in starting the onionshare server.
+ """
+ self.common.log('Mode', 'start_server_step3')
+
+ self.start_server_step3_custom()
+
+ if self.common.settings.get('shutdown_timeout'):
+ # Convert the date value to seconds between now and then
+ now = QtCore.QDateTime.currentDateTime()
+ self.timeout = now.secsTo(self.server_status.timeout)
+ # Set the shutdown timeout value
+ if self.timeout > 0:
+ self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout)
+ self.app.shutdown_timer.start()
+ # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
+ else:
+ self.stop_server()
+ self.start_server_error(strings._('gui_server_started_after_timeout'))
+
+ def start_server_step3_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def start_server_error(self, error):
+ """
+ If there's an error when trying to start the onion service
+ """
+ self.common.log('Mode', 'start_server_error')
+
+ Alert(self.common, error, QtWidgets.QMessageBox.Warning)
+ self.set_server_active.emit(False)
+ self.server_status.stop_server()
+ self.status_bar.clearMessage()
+
+ self.start_server_error_custom()
+
+ def start_server_error_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def cancel_server(self):
+ """
+ Cancel the server while it is preparing to start
+ """
+ self.cancel_server_custom()
+
+ if self.onion_thread:
+ self.common.log('Mode', 'cancel_server: quitting onion thread')
+ self.onion_thread.quit()
+ if self.web_thread:
+ self.common.log('Mode', 'cancel_server: quitting web thread')
+ self.web_thread.quit()
+ self.stop_server()
+
+ def cancel_server_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def stop_server(self):
+ """
+ Stop the onionshare server.
+ """
+ self.common.log('Mode', 'stop_server')
+
+ if self.server_status.status != ServerStatus.STATUS_STOPPED:
+ try:
+ self.web.stop(self.app.port)
+ except:
+ # Probably we had no port to begin with (Onion service didn't start)
+ pass
+ self.app.cleanup()
+
+ self.stop_server_custom()
+
+ self.set_server_active.emit(False)
+ self.stop_server_finished.emit()
+
+ def stop_server_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ def handle_tor_broke(self):
+ """
+ Handle connection from Tor breaking.
+ """
+ if self.server_status.status != ServerStatus.STATUS_STOPPED:
+ self.server_status.stop_server()
+ self.handle_tor_broke_custom()
+
+ def handle_tor_broke_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
+ # Handle web server events
+
+ def handle_request_load(self, event):
+ """
+ Handle REQUEST_LOAD event.
+ """
+ pass
+
+ def handle_request_started(self, event):
+ """
+ Handle REQUEST_STARTED event.
+ """
+ pass
+
+ def handle_request_rate_limit(self, event):
+ """
+ Handle REQUEST_RATE_LIMIT event.
+ """
+ self.stop_server()
+ Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
+
+ def handle_request_progress(self, event):
+ """
+ Handle REQUEST_PROGRESS event.
+ """
+ pass
+
+ def handle_request_canceled(self, event):
+ """
+ Handle REQUEST_CANCELED event.
+ """
+ pass
+
+ def handle_request_upload_file_renamed(self, event):
+ """
+ Handle REQUEST_UPLOAD_FILE_RENAMED event.
+ """
+ pass
+
+ def handle_request_upload_set_dir(self, event):
+ """
+ Handle REQUEST_UPLOAD_SET_DIR event.
+ """
+ pass
+
+ def handle_request_upload_finished(self, event):
+ """
+ Handle REQUEST_UPLOAD_FINISHED event.
+ """
+ pass
+
+ def handle_request_upload_canceled(self, event):
+ """
+ Handle REQUEST_UPLOAD_CANCELED event.
+ """
+ pass
diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py
new file mode 100644
index 00000000..1546cb68
--- /dev/null
+++ b/onionshare_gui/mode/history.py
@@ -0,0 +1,607 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import time
+import subprocess
+import os
+from datetime import datetime
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from onionshare import strings
+from ..widgets import Alert
+
+
+class HistoryItem(QtWidgets.QWidget):
+ """
+ The base history item
+ """
+ STATUS_STARTED = 0
+ STATUS_FINISHED = 1
+ STATUS_CANCELED = 2
+
+ def __init__(self):
+ super(HistoryItem, self).__init__()
+
+ def update(self):
+ pass
+
+ def cancel(self):
+ pass
+
+ def get_finished_label_text(self, started):
+ """
+ When an item finishes, returns a string displaying the start/end datetime range.
+ started is a datetime object.
+ """
+ return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started)
+
+ def get_canceled_label_text(self, started):
+ """
+ When an item is canceled, returns a string displaying the start/end datetime range.
+ started is a datetime object.
+ """
+ return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started)
+
+ def _get_label_text(self, string_name, string_range_name, started):
+ """
+ Return a string that contains a date, or date range.
+ """
+ ended = datetime.now()
+ if started.year == ended.year and started.month == ended.month and started.day == ended.day:
+ if started.hour == ended.hour and started.minute == ended.minute:
+ text = strings._(string_name).format(
+ started.strftime("%b %d, %I:%M%p")
+ )
+ else:
+ text = strings._(string_range_name).format(
+ started.strftime("%b %d, %I:%M%p"),
+ ended.strftime("%I:%M%p")
+ )
+ else:
+ text = strings._(string_range_name).format(
+ started.strftime("%b %d, %I:%M%p"),
+ ended.strftime("%b %d, %I:%M%p")
+ )
+ return text
+
+
+class ShareHistoryItem(HistoryItem):
+ """
+ Download history item, for share mode
+ """
+ def __init__(self, common, id, total_bytes):
+ super(ShareHistoryItem, self).__init__()
+ self.common = common
+
+ self.id = id
+ self.total_bytes = total_bytes
+ self.downloaded_bytes = 0
+ self.started = time.time()
+ self.started_dt = datetime.fromtimestamp(self.started)
+ self.status = HistoryItem.STATUS_STARTED
+
+ # Label
+ self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p")))
+
+ # Progress bar
+ self.progress_bar = QtWidgets.QProgressBar()
+ self.progress_bar.setTextVisible(True)
+ self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+ self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
+ self.progress_bar.setMinimum(0)
+ self.progress_bar.setMaximum(total_bytes)
+ self.progress_bar.setValue(0)
+ self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
+ self.progress_bar.total_bytes = total_bytes
+
+ # Layout
+ layout = QtWidgets.QVBoxLayout()
+ layout.addWidget(self.label)
+ layout.addWidget(self.progress_bar)
+ self.setLayout(layout)
+
+ # Start at 0
+ self.update(0)
+
+ def update(self, downloaded_bytes):
+ self.downloaded_bytes = downloaded_bytes
+
+ self.progress_bar.setValue(downloaded_bytes)
+ if downloaded_bytes == self.progress_bar.total_bytes:
+ pb_fmt = strings._('gui_all_modes_progress_complete').format(
+ self.common.format_seconds(time.time() - self.started))
+
+ # Change the label
+ self.label.setText(self.get_finished_label_text(self.started_dt))
+ self.status = HistoryItem.STATUS_FINISHED
+
+ else:
+ elapsed = time.time() - self.started
+ if elapsed < 10:
+ # Wait a couple of seconds for the download rate to stabilize.
+ # This prevents a "Windows copy dialog"-esque experience at
+ # the beginning of the download.
+ pb_fmt = strings._('gui_all_modes_progress_starting').format(
+ self.common.human_readable_filesize(downloaded_bytes))
+ else:
+ pb_fmt = strings._('gui_all_modes_progress_eta').format(
+ self.common.human_readable_filesize(downloaded_bytes),
+ self.estimated_time_remaining)
+
+ self.progress_bar.setFormat(pb_fmt)
+
+ def cancel(self):
+ self.progress_bar.setFormat(strings._('gui_canceled'))
+ self.status = HistoryItem.STATUS_CANCELED
+
+ @property
+ def estimated_time_remaining(self):
+ return self.common.estimated_time_remaining(self.downloaded_bytes,
+ self.total_bytes,
+ self.started)
+
+
+class ReceiveHistoryItemFile(QtWidgets.QWidget):
+ def __init__(self, common, filename):
+ super(ReceiveHistoryItemFile, self).__init__()
+ self.common = common
+
+ self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename))
+
+ self.filename = filename
+ self.dir = None
+ self.started = datetime.now()
+
+ # Filename label
+ self.filename_label = QtWidgets.QLabel(self.filename)
+ self.filename_label_width = self.filename_label.width()
+
+ # File size label
+ self.filesize_label = QtWidgets.QLabel()
+ self.filesize_label.setStyleSheet(self.common.css['receive_file_size'])
+ self.filesize_label.hide()
+
+ # Folder button
+ folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png')))
+ folder_icon = QtGui.QIcon(folder_pixmap)
+ self.folder_button = QtWidgets.QPushButton()
+ self.folder_button.clicked.connect(self.open_folder)
+ self.folder_button.setIcon(folder_icon)
+ self.folder_button.setIconSize(folder_pixmap.rect().size())
+ self.folder_button.setFlat(True)
+ self.folder_button.hide()
+
+ # Layouts
+ layout = QtWidgets.QHBoxLayout()
+ layout.addWidget(self.filename_label)
+ layout.addWidget(self.filesize_label)
+ layout.addStretch()
+ layout.addWidget(self.folder_button)
+ self.setLayout(layout)
+
+ def update(self, uploaded_bytes, complete):
+ self.filesize_label.setText(self.common.human_readable_filesize(uploaded_bytes))
+ self.filesize_label.show()
+
+ if complete:
+ self.folder_button.show()
+
+ def rename(self, new_filename):
+ self.filename = new_filename
+ self.filename_label.setText(self.filename)
+
+ def set_dir(self, dir):
+ self.dir = dir
+
+ def open_folder(self):
+ """
+ Open the downloads folder, with the file selected, in a cross-platform manner
+ """
+ self.common.log('ReceiveHistoryItemFile', 'open_folder')
+
+ if not self.dir:
+ self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder")
+ return
+
+ abs_filename = os.path.join(self.dir, self.filename)
+
+ # Linux
+ if self.common.platform == 'Linux' or self.common.platform == 'BSD':
+ try:
+ # If nautilus is available, open it
+ subprocess.Popen(['nautilus', abs_filename])
+ except:
+ Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename))
+
+ # macOS
+ elif self.common.platform == 'Darwin':
+ subprocess.call(['open', '-R', abs_filename])
+
+ # Windows
+ elif self.common.platform == 'Windows':
+ subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)])
+
+class ReceiveHistoryItem(HistoryItem):
+ def __init__(self, common, id, content_length):
+ super(ReceiveHistoryItem, self).__init__()
+ self.common = common
+ self.id = id
+ self.content_length = content_length
+ self.started = datetime.now()
+ self.status = HistoryItem.STATUS_STARTED
+
+ # Label
+ self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p")))
+
+ # Progress bar
+ self.progress_bar = QtWidgets.QProgressBar()
+ self.progress_bar.setTextVisible(True)
+ self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+ self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
+ self.progress_bar.setMinimum(0)
+ self.progress_bar.setValue(0)
+ self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
+
+ # This layout contains file widgets
+ self.files_layout = QtWidgets.QVBoxLayout()
+ self.files_layout.setContentsMargins(0, 0, 0, 0)
+ files_widget = QtWidgets.QWidget()
+ files_widget.setStyleSheet(self.common.css['receive_file'])
+ files_widget.setLayout(self.files_layout)
+
+ # Layout
+ layout = QtWidgets.QVBoxLayout()
+ layout.addWidget(self.label)
+ layout.addWidget(self.progress_bar)
+ layout.addWidget(files_widget)
+ layout.addStretch()
+ self.setLayout(layout)
+
+ # We're also making a dictionary of file widgets, to make them easier to access
+ self.files = {}
+
+ def update(self, data):
+ """
+ Using the progress from Web, update the progress bar and file size labels
+ for each file
+ """
+ if data['action'] == 'progress':
+ total_uploaded_bytes = 0
+ for filename in data['progress']:
+ total_uploaded_bytes += data['progress'][filename]['uploaded_bytes']
+
+ # Update the progress bar
+ self.progress_bar.setMaximum(self.content_length)
+ self.progress_bar.setValue(total_uploaded_bytes)
+
+ elapsed = datetime.now() - self.started
+ if elapsed.seconds < 10:
+ pb_fmt = strings._('gui_all_modes_progress_starting').format(
+ self.common.human_readable_filesize(total_uploaded_bytes))
+ else:
+ estimated_time_remaining = self.common.estimated_time_remaining(
+ total_uploaded_bytes,
+ self.content_length,
+ self.started.timestamp())
+ pb_fmt = strings._('gui_all_modes_progress_eta').format(
+ self.common.human_readable_filesize(total_uploaded_bytes),
+ estimated_time_remaining)
+
+ # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration"
+ for filename in list(data['progress']):
+ # Add a new file if needed
+ if filename not in self.files:
+ self.files[filename] = ReceiveHistoryItemFile(self.common, filename)
+ self.files_layout.addWidget(self.files[filename])
+
+ # Update the file
+ self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete'])
+
+ elif data['action'] == 'rename':
+ self.files[data['old_filename']].rename(data['new_filename'])
+ self.files[data['new_filename']] = self.files.pop(data['old_filename'])
+
+ elif data['action'] == 'set_dir':
+ self.files[data['filename']].set_dir(data['dir'])
+
+ elif data['action'] == 'finished':
+ # Change the status
+ self.status = HistoryItem.STATUS_FINISHED
+
+ # Hide the progress bar
+ self.progress_bar.hide()
+
+ # Change the label
+ self.label.setText(self.get_finished_label_text(self.started))
+
+ elif data['action'] == 'canceled':
+ # Change the status
+ self.status = HistoryItem.STATUS_CANCELED
+
+ # Hide the progress bar
+ self.progress_bar.hide()
+
+ # Change the label
+ self.label.setText(self.get_canceled_label_text(self.started))
+
+
+class HistoryItemList(QtWidgets.QScrollArea):
+ """
+ List of items
+ """
+ def __init__(self, common):
+ super(HistoryItemList, self).__init__()
+ self.common = common
+
+ self.items = {}
+
+ # The layout that holds all of the items
+ self.items_layout = QtWidgets.QVBoxLayout()
+ self.items_layout.setContentsMargins(0, 0, 0, 0)
+ self.items_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
+
+ # Wrapper layout that also contains a stretch
+ wrapper_layout = QtWidgets.QVBoxLayout()
+ wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
+ wrapper_layout.addLayout(self.items_layout)
+ wrapper_layout.addStretch()
+
+ # The internal widget of the scroll area
+ widget = QtWidgets.QWidget()
+ widget.setLayout(wrapper_layout)
+ self.setWidget(widget)
+ self.setWidgetResizable(True)
+
+ # Other scroll area settings
+ self.setBackgroundRole(QtGui.QPalette.Light)
+ self.verticalScrollBar().rangeChanged.connect(self.resizeScroll)
+
+ def resizeScroll(self, minimum, maximum):
+ """
+ Scroll to the bottom of the window when the range changes.
+ """
+ self.verticalScrollBar().setValue(maximum)
+
+ def add(self, id, item):
+ """
+ Add a new item. Override this method.
+ """
+ self.items[id] = item
+ self.items_layout.addWidget(item)
+
+ def update(self, id, data):
+ """
+ Update an item. Override this method.
+ """
+ if id in self.items:
+ self.items[id].update(data)
+
+ def cancel(self, id):
+ """
+ Cancel an item. Override this method.
+ """
+ if id in self.items:
+ self.items[id].cancel()
+
+ def reset(self):
+ """
+ Reset all items, emptying the list. Override this method.
+ """
+ for key, item in self.items.copy().items():
+ if item.status != HistoryItem.STATUS_STARTED:
+ self.items_layout.removeWidget(item)
+ item.close()
+ del self.items[key]
+
+class History(QtWidgets.QWidget):
+ """
+ A history of what's happened so far in this mode. This contains an internal
+ object full of a scrollable list of items.
+ """
+ def __init__(self, common, empty_image, empty_text, header_text):
+ super(History, self).__init__()
+ self.common = common
+
+ self.setMinimumWidth(350)
+
+ # In progress and completed counters
+ self.in_progress_count = 0
+ self.completed_count = 0
+
+ # In progress and completed labels
+ self.in_progress_label = QtWidgets.QLabel()
+ self.in_progress_label.setStyleSheet(self.common.css['mode_info_label'])
+ self.completed_label = QtWidgets.QLabel()
+ self.completed_label.setStyleSheet(self.common.css['mode_info_label'])
+
+ # Header
+ self.header_label = QtWidgets.QLabel(header_text)
+ self.header_label.setStyleSheet(self.common.css['downloads_uploads_label'])
+ clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history'))
+ clear_button.setStyleSheet(self.common.css['downloads_uploads_clear'])
+ clear_button.setFlat(True)
+ clear_button.clicked.connect(self.reset)
+ header_layout = QtWidgets.QHBoxLayout()
+ header_layout.addWidget(self.header_label)
+ header_layout.addStretch()
+ header_layout.addWidget(self.in_progress_label)
+ header_layout.addWidget(self.completed_label)
+ header_layout.addWidget(clear_button)
+
+ # When there are no items
+ self.empty_image = QtWidgets.QLabel()
+ self.empty_image.setAlignment(QtCore.Qt.AlignCenter)
+ self.empty_image.setPixmap(empty_image)
+ self.empty_text = QtWidgets.QLabel(empty_text)
+ self.empty_text.setAlignment(QtCore.Qt.AlignCenter)
+ self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text'])
+ empty_layout = QtWidgets.QVBoxLayout()
+ empty_layout.addStretch()
+ empty_layout.addWidget(self.empty_image)
+ empty_layout.addWidget(self.empty_text)
+ empty_layout.addStretch()
+ self.empty = QtWidgets.QWidget()
+ self.empty.setStyleSheet(self.common.css['downloads_uploads_empty'])
+ self.empty.setLayout(empty_layout)
+
+ # When there are items
+ self.item_list = HistoryItemList(self.common)
+ self.not_empty_layout = QtWidgets.QVBoxLayout()
+ self.not_empty_layout.addLayout(header_layout)
+ self.not_empty_layout.addWidget(self.item_list)
+ self.not_empty = QtWidgets.QWidget()
+ self.not_empty.setLayout(self.not_empty_layout)
+
+ # Layout
+ layout = QtWidgets.QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.addWidget(self.empty)
+ layout.addWidget(self.not_empty)
+ self.setLayout(layout)
+
+ # Reset once at the beginning
+ self.reset()
+
+ def add(self, id, item):
+ """
+ Add a new item.
+ """
+ self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item))
+
+ # Hide empty, show not empty
+ self.empty.hide()
+ self.not_empty.show()
+
+ # Add it to the list
+ self.item_list.add(id, item)
+
+ def update(self, id, data):
+ """
+ Update an item.
+ """
+ self.item_list.update(id, data)
+
+ def cancel(self, id):
+ """
+ Cancel an item.
+ """
+ self.item_list.cancel(id)
+
+ def reset(self):
+ """
+ Reset all items.
+ """
+ self.item_list.reset()
+ if len(self.item_list.items) == 0:
+ # Hide not empty, show empty
+ self.not_empty.hide()
+ self.empty.show()
+ # Reset in-progress counter
+ self.in_progress_count = 0
+ self.update_in_progress()
+
+ # Reset completed counter
+ self.completed_count = 0
+ self.update_completed()
+
+ def update_completed(self):
+ """
+ Update the 'completed' widget.
+ """
+ if self.completed_count == 0:
+ image = self.common.get_resource_path('images/share_completed_none.png')
+ else:
+ image = self.common.get_resource_path('images/share_completed.png')
+ self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count))
+ self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count))
+
+ def update_in_progress(self):
+ """
+ Update the 'in progress' widget.
+ """
+ if self.in_progress_count == 0:
+ image = self.common.get_resource_path('images/share_in_progress_none.png')
+ else:
+ image = self.common.get_resource_path('images/share_in_progress.png')
+ self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
+ self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
+
+
+class ToggleHistory(QtWidgets.QPushButton):
+ """
+ Widget for toggling showing or hiding the history, as well as keeping track
+ of the indicator counter if it's hidden
+ """
+ def __init__(self, common, current_mode, history_widget, icon, selected_icon):
+ super(ToggleHistory, self).__init__()
+ self.common = common
+ self.current_mode = current_mode
+ self.history_widget = history_widget
+ self.icon = icon
+ self.selected_icon = selected_icon
+
+ # Toggle button
+ self.setDefault(False)
+ self.setFixedWidth(35)
+ self.setFixedHeight(30)
+ self.setFlat(True)
+ self.setIcon(icon)
+ self.clicked.connect(self.toggle_clicked)
+
+ # Keep track of indicator
+ self.indicator_count = 0
+ self.indicator_label = QtWidgets.QLabel(parent=self)
+ self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator'])
+ self.update_indicator()
+
+ def update_indicator(self, increment=False):
+ """
+ Update the display of the indicator count. If increment is True, then
+ only increment the counter if Downloads is hidden.
+ """
+ if increment and not self.history_widget.isVisible():
+ self.indicator_count += 1
+
+ self.indicator_label.setText("{}".format(self.indicator_count))
+
+ if self.indicator_count == 0:
+ self.indicator_label.hide()
+ else:
+ size = self.indicator_label.sizeHint()
+ self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height())
+ self.indicator_label.show()
+
+ def toggle_clicked(self):
+ """
+ Toggle showing and hiding the history widget
+ """
+ self.common.log('ToggleHistory', 'toggle_clicked')
+
+ if self.history_widget.isVisible():
+ self.history_widget.hide()
+ self.setIcon(self.icon)
+ self.setFlat(True)
+ else:
+ self.history_widget.show()
+ self.setIcon(self.selected_icon)
+ self.setFlat(False)
+
+ # Reset the indicator count
+ self.indicator_count = 0
+ self.update_indicator()
diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py
new file mode 100644
index 00000000..5fb33ab3
--- /dev/null
+++ b/onionshare_gui/mode/receive_mode/__init__.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from onionshare import strings
+from onionshare.web import Web
+
+from ..history import History, ToggleHistory, ReceiveHistoryItem
+from .. import Mode
+
+class ReceiveMode(Mode):
+ """
+ Parts of the main window UI for receiving files.
+ """
+ def init(self):
+ """
+ Custom initialization for ReceiveMode.
+ """
+ # Create the Web object
+ self.web = Web(self.common, True, 'receive')
+
+ # Server status
+ self.server_status.set_mode('receive')
+ self.server_status.server_started_finished.connect(self.update_primary_action)
+ self.server_status.server_stopped.connect(self.update_primary_action)
+ self.server_status.server_canceled.connect(self.update_primary_action)
+
+ # Tell server_status about web, then update
+ self.server_status.web = self.web
+ self.server_status.update()
+
+ # Upload history
+ self.history = History(
+ self.common,
+ QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))),
+ strings._('gui_receive_mode_no_files'),
+ strings._('gui_all_modes_history')
+ )
+ self.history.hide()
+
+ # Toggle history
+ self.toggle_history = ToggleHistory(
+ self.common, self, self.history,
+ QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')),
+ QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png'))
+ )
+
+ # Receive mode warning
+ receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning'))
+ receive_warning.setMinimumHeight(80)
+ receive_warning.setWordWrap(True)
+
+ # Top bar
+ top_bar_layout = QtWidgets.QHBoxLayout()
+ top_bar_layout.addStretch()
+ top_bar_layout.addWidget(self.toggle_history)
+
+ # Main layout
+ self.main_layout = QtWidgets.QVBoxLayout()
+ self.main_layout.addLayout(top_bar_layout)
+ self.main_layout.addWidget(receive_warning)
+ self.main_layout.addWidget(self.primary_action)
+ self.main_layout.addStretch()
+ self.main_layout.addWidget(self.min_width_widget)
+
+ # Wrapper layout
+ self.wrapper_layout = QtWidgets.QHBoxLayout()
+ self.wrapper_layout.addLayout(self.main_layout)
+ self.wrapper_layout.addWidget(self.history, stretch=1)
+ self.setLayout(self.wrapper_layout)
+
+ def get_stop_server_shutdown_timeout_text(self):
+ """
+ Return the string to put on the stop server button, if there's a shutdown timeout
+ """
+ return strings._('gui_receive_stop_server_shutdown_timeout')
+
+ def timeout_finished_should_stop_server(self):
+ """
+ The shutdown timer expired, should we stop the server? Returns a bool
+ """
+ # If there were no attempts to upload files, or all uploads are done, we can stop
+ if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress:
+ self.server_status.stop_server()
+ self.server_status_label.setText(strings._('close_on_timeout'))
+ return True
+ # An upload is probably still running - hold off on stopping the share, but block new shares.
+ else:
+ self.server_status_label.setText(strings._('gui_receive_mode_timeout_waiting'))
+ self.web.receive_mode.can_upload = False
+ return False
+
+ def start_server_custom(self):
+ """
+ Starting the server.
+ """
+ # Reset web counters
+ self.web.receive_mode.upload_count = 0
+ self.web.error404_count = 0
+
+ # Hide and reset the uploads if we have previously shared
+ self.reset_info_counters()
+
+ def start_server_step2_custom(self):
+ """
+ Step 2 in starting the server.
+ """
+ # Continue
+ self.starting_server_step3.emit()
+ self.start_server_finished.emit()
+
+ def handle_tor_broke_custom(self):
+ """
+ Connection to Tor broke.
+ """
+ self.primary_action.hide()
+
+ def handle_request_load(self, event):
+ """
+ Handle REQUEST_LOAD event.
+ """
+ self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message'))
+
+ def handle_request_started(self, event):
+ """
+ Handle REQUEST_STARTED event.
+ """
+ item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"])
+ self.history.add(event["data"]["id"], item)
+ self.toggle_history.update_indicator(True)
+ self.history.in_progress_count += 1
+ self.history.update_in_progress()
+
+ self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message'))
+
+ def handle_request_progress(self, event):
+ """
+ Handle REQUEST_PROGRESS event.
+ """
+ self.history.update(event["data"]["id"], {
+ 'action': 'progress',
+ 'progress': event["data"]["progress"]
+ })
+
+ def handle_request_upload_file_renamed(self, event):
+ """
+ Handle REQUEST_UPLOAD_FILE_RENAMED event.
+ """
+ self.history.update(event["data"]["id"], {
+ 'action': 'rename',
+ 'old_filename': event["data"]["old_filename"],
+ 'new_filename': event["data"]["new_filename"]
+ })
+
+ def handle_request_upload_set_dir(self, event):
+ """
+ Handle REQUEST_UPLOAD_SET_DIR event.
+ """
+ self.history.update(event["data"]["id"], {
+ 'action': 'set_dir',
+ 'filename': event["data"]["filename"],
+ 'dir': event["data"]["dir"]
+ })
+
+ def handle_request_upload_finished(self, event):
+ """
+ Handle REQUEST_UPLOAD_FINISHED event.
+ """
+ self.history.update(event["data"]["id"], {
+ 'action': 'finished'
+ })
+ self.history.completed_count += 1
+ self.history.in_progress_count -= 1
+ self.history.update_completed()
+ self.history.update_in_progress()
+
+ def handle_request_upload_canceled(self, event):
+ """
+ Handle REQUEST_UPLOAD_CANCELED event.
+ """
+ self.history.update(event["data"]["id"], {
+ 'action': 'canceled'
+ })
+ self.history.in_progress_count -= 1
+ self.history.update_in_progress()
+
+ def on_reload_settings(self):
+ """
+ We should be ok to re-enable the 'Start Receive Mode' button now.
+ """
+ self.primary_action.show()
+
+ def reset_info_counters(self):
+ """
+ Set the info counters back to zero.
+ """
+ self.history.reset()
+
+ def update_primary_action(self):
+ self.common.log('ReceiveMode', 'update_primary_action')
diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py
new file mode 100644
index 00000000..1f5ad00b
--- /dev/null
+++ b/onionshare_gui/mode/share_mode/__init__.py
@@ -0,0 +1,382 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import os
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from onionshare import strings
+from onionshare.onion import *
+from onionshare.common import Common
+from onionshare.web import Web
+
+from .file_selection import FileSelection
+from .threads import CompressThread
+from .. import Mode
+from ..history import History, ToggleHistory, ShareHistoryItem
+from ...widgets import Alert
+
+
+class ShareMode(Mode):
+ """
+ Parts of the main window UI for sharing files.
+ """
+ def init(self):
+ """
+ Custom initialization for ReceiveMode.
+ """
+ # Threads start out as None
+ self.compress_thread = None
+
+ # Create the Web object
+ self.web = Web(self.common, True, 'share')
+
+ # File selection
+ self.file_selection = FileSelection(self.common, self)
+ if self.filenames:
+ for filename in self.filenames:
+ self.file_selection.file_list.add_file(filename)
+
+ # Server status
+ self.server_status.set_mode('share', self.file_selection)
+ self.server_status.server_started.connect(self.file_selection.server_started)
+ self.server_status.server_stopped.connect(self.file_selection.server_stopped)
+ self.server_status.server_stopped.connect(self.update_primary_action)
+ self.server_status.server_canceled.connect(self.file_selection.server_stopped)
+ self.server_status.server_canceled.connect(self.update_primary_action)
+ self.file_selection.file_list.files_updated.connect(self.server_status.update)
+ self.file_selection.file_list.files_updated.connect(self.update_primary_action)
+ # Tell server_status about web, then update
+ self.server_status.web = self.web
+ self.server_status.update()
+
+ # Filesize warning
+ self.filesize_warning = QtWidgets.QLabel()
+ self.filesize_warning.setWordWrap(True)
+ self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning'])
+ self.filesize_warning.hide()
+
+ # Download history
+ self.history = History(
+ self.common,
+ QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))),
+ strings._('gui_share_mode_no_files'),
+ strings._('gui_all_modes_history')
+ )
+ self.history.hide()
+
+ # Info label
+ self.info_label = QtWidgets.QLabel()
+ self.info_label.hide()
+
+ # Toggle history
+ self.toggle_history = ToggleHistory(
+ self.common, self, self.history,
+ QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')),
+ QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png'))
+ )
+
+ # Top bar
+ top_bar_layout = QtWidgets.QHBoxLayout()
+ top_bar_layout.addWidget(self.info_label)
+ top_bar_layout.addStretch()
+ top_bar_layout.addWidget(self.toggle_history)
+
+ # Primary action layout
+ self.primary_action_layout.addWidget(self.filesize_warning)
+ self.primary_action.hide()
+ self.update_primary_action()
+
+ # Status bar, zip progress bar
+ self._zip_progress_bar = None
+
+ # Main layout
+ self.main_layout = QtWidgets.QVBoxLayout()
+ self.main_layout.addLayout(top_bar_layout)
+ self.main_layout.addLayout(self.file_selection)
+ self.main_layout.addWidget(self.primary_action)
+ self.main_layout.addWidget(self.min_width_widget)
+
+ # Wrapper layout
+ self.wrapper_layout = QtWidgets.QHBoxLayout()
+ self.wrapper_layout.addLayout(self.main_layout)
+ self.wrapper_layout.addWidget(self.history, stretch=1)
+ self.setLayout(self.wrapper_layout)
+
+ # Always start with focus on file selection
+ self.file_selection.setFocus()
+
+ def get_stop_server_shutdown_timeout_text(self):
+ """
+ Return the string to put on the stop server button, if there's a shutdown timeout
+ """
+ return strings._('gui_share_stop_server_shutdown_timeout')
+
+ def timeout_finished_should_stop_server(self):
+ """
+ The shutdown timer expired, should we stop the server? Returns a bool
+ """
+ # If there were no attempts to download the share, or all downloads are done, we can stop
+ if self.web.share_mode.download_count == 0 or self.web.done:
+ self.server_status.stop_server()
+ self.server_status_label.setText(strings._('close_on_timeout'))
+ return True
+ # A download is probably still running - hold off on stopping the share
+ else:
+ self.server_status_label.setText(strings._('gui_share_mode_timeout_waiting'))
+ return False
+
+ def start_server_custom(self):
+ """
+ Starting the server.
+ """
+ # Reset web counters
+ self.web.share_mode.download_count = 0
+ self.web.error404_count = 0
+
+ # Hide and reset the downloads if we have previously shared
+ self.reset_info_counters()
+
+ def start_server_step2_custom(self):
+ """
+ Step 2 in starting the server. Zipping up files.
+ """
+ # Add progress bar to the status bar, indicating the compressing of files.
+ self._zip_progress_bar = ZipProgressBar(self.common, 0)
+ self.filenames = []
+ for index in range(self.file_selection.file_list.count()):
+ self.filenames.append(self.file_selection.file_list.item(index).filename)
+
+ self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
+ self.status_bar.insertWidget(0, self._zip_progress_bar)
+
+ # prepare the files for sending in a new thread
+ self.compress_thread = CompressThread(self)
+ self.compress_thread.success.connect(self.starting_server_step3.emit)
+ self.compress_thread.success.connect(self.start_server_finished.emit)
+ self.compress_thread.error.connect(self.starting_server_error.emit)
+ self.server_status.server_canceled.connect(self.compress_thread.cancel)
+ self.compress_thread.start()
+
+ def start_server_step3_custom(self):
+ """
+ Step 3 in starting the server. Remove zip progess bar, and display large filesize
+ warning, if applicable.
+ """
+ # Remove zip progress bar
+ if self._zip_progress_bar is not None:
+ self.status_bar.removeWidget(self._zip_progress_bar)
+ self._zip_progress_bar = None
+
+ # Warn about sending large files over Tor
+ if self.web.share_mode.download_filesize >= 157286400: # 150mb
+ self.filesize_warning.setText(strings._("large_filesize"))
+ self.filesize_warning.show()
+
+ def start_server_error_custom(self):
+ """
+ Start server error.
+ """
+ if self._zip_progress_bar is not None:
+ self.status_bar.removeWidget(self._zip_progress_bar)
+ self._zip_progress_bar = None
+
+ def stop_server_custom(self):
+ """
+ Stop server.
+ """
+ # Remove the progress bar
+ if self._zip_progress_bar is not None:
+ self.status_bar.removeWidget(self._zip_progress_bar)
+ self._zip_progress_bar = None
+
+ self.filesize_warning.hide()
+ self.history.in_progress_count = 0
+ self.history.completed_count = 0
+ self.history.update_in_progress()
+ self.file_selection.file_list.adjustSize()
+
+ def cancel_server_custom(self):
+ """
+ Stop the compression thread on cancel
+ """
+ if self.compress_thread:
+ self.common.log('ShareMode', 'cancel_server: quitting compress thread')
+ self.compress_thread.quit()
+
+ def handle_tor_broke_custom(self):
+ """
+ Connection to Tor broke.
+ """
+ self.primary_action.hide()
+
+ def handle_request_load(self, event):
+ """
+ Handle REQUEST_LOAD event.
+ """
+ self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message'))
+
+ def handle_request_started(self, event):
+ """
+ Handle REQUEST_STARTED event.
+ """
+ if event["data"]["use_gzip"]:
+ filesize = self.web.share_mode.gzip_filesize
+ else:
+ filesize = self.web.share_mode.download_filesize
+
+ item = ShareHistoryItem(self.common, event["data"]["id"], filesize)
+ self.history.add(event["data"]["id"], item)
+ self.toggle_history.update_indicator(True)
+ self.history.in_progress_count += 1
+ self.history.update_in_progress()
+
+ self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message'))
+
+ def handle_request_progress(self, event):
+ """
+ Handle REQUEST_PROGRESS event.
+ """
+ self.history.update(event["data"]["id"], event["data"]["bytes"])
+
+ # Is the download complete?
+ if event["data"]["bytes"] == self.web.share_mode.filesize:
+ self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message'))
+
+ # Update completed and in progress labels
+ self.history.completed_count += 1
+ self.history.in_progress_count -= 1
+ self.history.update_completed()
+ self.history.update_in_progress()
+
+ # Close on finish?
+ if self.common.settings.get('close_after_first_download'):
+ self.server_status.stop_server()
+ self.status_bar.clearMessage()
+ self.server_status_label.setText(strings._('closing_automatically'))
+ else:
+ if self.server_status.status == self.server_status.STATUS_STOPPED:
+ self.history.cancel(event["data"]["id"])
+ self.history.in_progress_count = 0
+ self.history.update_in_progress()
+
+ def handle_request_canceled(self, event):
+ """
+ Handle REQUEST_CANCELED event.
+ """
+ self.history.cancel(event["data"]["id"])
+
+ # Update in progress count
+ self.history.in_progress_count -= 1
+ self.history.update_in_progress()
+ self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message'))
+
+ def on_reload_settings(self):
+ """
+ If there were some files listed for sharing, we should be ok to re-enable
+ the 'Start Sharing' button now.
+ """
+ if self.server_status.file_selection.get_num_files() > 0:
+ self.primary_action.show()
+ self.info_label.show()
+
+ def update_primary_action(self):
+ self.common.log('ShareMode', 'update_primary_action')
+
+ # Show or hide primary action layout
+ file_count = self.file_selection.file_list.count()
+ if file_count > 0:
+ self.primary_action.show()
+ self.info_label.show()
+
+ # Update the file count in the info label
+ total_size_bytes = 0
+ for index in range(self.file_selection.file_list.count()):
+ item = self.file_selection.file_list.item(index)
+ total_size_bytes += item.size_bytes
+ total_size_readable = self.common.human_readable_filesize(total_size_bytes)
+
+ if file_count > 1:
+ self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
+ else:
+ self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
+
+ else:
+ self.primary_action.hide()
+ self.info_label.hide()
+
+ def reset_info_counters(self):
+ """
+ Set the info counters back to zero.
+ """
+ self.history.reset()
+
+ @staticmethod
+ def _compute_total_size(filenames):
+ total_size = 0
+ for filename in filenames:
+ if os.path.isfile(filename):
+ total_size += os.path.getsize(filename)
+ if os.path.isdir(filename):
+ total_size += Common.dir_size(filename)
+ return total_size
+
+
+class ZipProgressBar(QtWidgets.QProgressBar):
+ update_processed_size_signal = QtCore.pyqtSignal(int)
+
+ def __init__(self, common, total_files_size):
+ super(ZipProgressBar, self).__init__()
+ self.common = common
+
+ self.setMaximumHeight(20)
+ self.setMinimumWidth(200)
+ self.setValue(0)
+ self.setFormat(strings._('zip_progress_bar_format'))
+ self.setStyleSheet(self.common.css['share_zip_progess_bar'])
+
+ self._total_files_size = total_files_size
+ self._processed_size = 0
+
+ self.update_processed_size_signal.connect(self.update_processed_size)
+
+ @property
+ def total_files_size(self):
+ return self._total_files_size
+
+ @total_files_size.setter
+ def total_files_size(self, val):
+ self._total_files_size = val
+
+ @property
+ def processed_size(self):
+ return self._processed_size
+
+ @processed_size.setter
+ def processed_size(self, val):
+ self.update_processed_size(val)
+
+ def update_processed_size(self, val):
+ self._processed_size = val
+
+ if self.processed_size < self.total_files_size:
+ self.setValue(int((self.processed_size * 100) / self.total_files_size))
+ elif self.total_files_size != 0:
+ self.setValue(100)
+ else:
+ self.setValue(0)
diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py
index 3f52a96d..0d4229fe 100644
--- a/onionshare_gui/file_selection.py
+++ b/onionshare_gui/mode/share_mode/file_selection.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,26 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
from PyQt5 import QtCore, QtWidgets, QtGui
-from .alert import Alert
-from onionshare import strings, common
+from onionshare import strings
+
+from ...widgets import Alert, AddFileDialog
class DropHereLabel(QtWidgets.QLabel):
"""
When there are no files or folders in the FileList yet, display the
'drop files here' message and graphic.
"""
- def __init__(self, parent, image=False):
+ def __init__(self, common, parent, image=False):
self.parent = parent
super(DropHereLabel, self).__init__(parent=parent)
+
+ self.common = common
+
self.setAcceptDrops(True)
self.setAlignment(QtCore.Qt.AlignCenter)
if image:
- self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(common.get_resource_path('images/logo_transparent.png'))))
+ self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png'))))
else:
- self.setText(strings._('gui_drag_and_drop', True))
- self.setStyleSheet('color: #999999;')
+ self.setText(strings._('gui_drag_and_drop'))
+ self.setStyleSheet(self.common.css['share_file_selection_drop_here_label'])
self.hide()
@@ -53,13 +57,16 @@ class DropCountLabel(QtWidgets.QLabel):
While dragging files over the FileList, this counter displays the
number of files you're dragging.
"""
- def __init__(self, parent):
+ def __init__(self, common, parent):
self.parent = parent
super(DropCountLabel, self).__init__(parent=parent)
+
+ self.common = common
+
self.setAcceptDrops(True)
self.setAlignment(QtCore.Qt.AlignCenter)
- self.setText(strings._('gui_drag_and_drop', True))
- self.setStyleSheet('color: #ffffff; background-color: #f44449; font-weight: bold; padding: 5px 10px; border-radius: 10px;')
+ self.setText(strings._('gui_drag_and_drop'))
+ self.setStyleSheet(self.common.css['share_file_selection_drop_count_label'])
self.hide()
def dragEnterEvent(self, event):
@@ -74,16 +81,19 @@ class FileList(QtWidgets.QListWidget):
files_dropped = QtCore.pyqtSignal()
files_updated = QtCore.pyqtSignal()
- def __init__(self, parent=None):
+ def __init__(self, common, parent=None):
super(FileList, self).__init__(parent)
+
+ self.common = common
+
self.setAcceptDrops(True)
self.setIconSize(QtCore.QSize(32, 32))
self.setSortingEnabled(True)
- self.setMinimumHeight(205)
+ self.setMinimumHeight(160)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
- self.drop_here_image = DropHereLabel(self, True)
- self.drop_here_text = DropHereLabel(self, False)
- self.drop_count = DropCountLabel(self)
+ self.drop_here_image = DropHereLabel(self.common, self, True)
+ self.drop_here_text = DropHereLabel(self.common, self, False)
+ self.drop_count = DropCountLabel(self.common, self)
self.resizeEvent(None)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
@@ -148,7 +158,7 @@ class FileList(QtWidgets.QListWidget):
dragEnterEvent for dragging files and directories into the widget.
"""
if event.mimeData().hasUrls:
- self.setStyleSheet('FileList { border: 3px solid #538ad0; }')
+ self.setStyleSheet(self.common.css['share_file_list_drag_enter'])
count = len(event.mimeData().urls())
self.drop_count.setText('+{}'.format(count))
@@ -163,7 +173,7 @@ class FileList(QtWidgets.QListWidget):
"""
dragLeaveEvent for dragging files and directories into the widget.
"""
- self.setStyleSheet('FileList { border: none; }')
+ self.setStyleSheet(self.common.css['share_file_list_drag_leave'])
self.drop_count.hide()
event.accept()
self.update()
@@ -191,7 +201,7 @@ class FileList(QtWidgets.QListWidget):
else:
event.ignore()
- self.setStyleSheet('border: none;')
+ self.setStyleSheet(self.common.css['share_file_list_drag_leave'])
self.drop_count.hide()
self.files_dropped.emit()
@@ -206,7 +216,7 @@ class FileList(QtWidgets.QListWidget):
if filename not in filenames:
if not os.access(filename, os.R_OK):
- Alert(strings._("not_a_readable_file", True).format(filename))
+ Alert(self.common, strings._("not_a_readable_file").format(filename))
return
fileinfo = QtCore.QFileInfo(filename)
@@ -215,10 +225,10 @@ class FileList(QtWidgets.QListWidget):
if os.path.isfile(filename):
size_bytes = fileinfo.size()
- size_readable = common.human_readable_filesize(size_bytes)
+ size_readable = self.common.human_readable_filesize(size_bytes)
else:
- size_bytes = common.dir_size(filename)
- size_readable = common.human_readable_filesize(size_bytes)
+ size_bytes = self.common.dir_size(filename)
+ size_readable = self.common.human_readable_filesize(size_bytes)
# Create a new item
item = QtWidgets.QListWidgetItem()
@@ -228,7 +238,7 @@ class FileList(QtWidgets.QListWidget):
# Item's filename attribute and size labels
item.filename = filename
item_size = QtWidgets.QLabel(size_readable)
- item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }')
+ item_size.setStyleSheet(self.common.css['share_file_list_item_size'])
item.basename = os.path.basename(filename.rstrip('/'))
# Use the basename as the method with which to sort the list
@@ -245,12 +255,13 @@ class FileList(QtWidgets.QListWidget):
item.item_button = QtWidgets.QPushButton()
item.item_button.setDefault(False)
item.item_button.setFlat(True)
- item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) )
+ item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) )
item.item_button.clicked.connect(delete_item)
item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
# Item info widget, with a white background
item_info_layout = QtWidgets.QHBoxLayout()
+ item_info_layout.setContentsMargins(0, 0, 0, 0)
item_info_layout.addWidget(item_size)
item_info_layout.addWidget(item.item_button)
item_info = QtWidgets.QWidget()
@@ -277,24 +288,40 @@ class FileSelection(QtWidgets.QVBoxLayout):
The list of files and folders in the GUI, as well as buttons to add and
delete the files and folders.
"""
- def __init__(self):
+ def __init__(self, common, parent):
super(FileSelection, self).__init__()
+
+ self.common = common
+ self.parent = parent
+
self.server_on = False
# File list
- self.file_list = FileList()
+ self.file_list = FileList(self.common)
self.file_list.itemSelectionChanged.connect(self.update)
self.file_list.files_dropped.connect(self.update)
self.file_list.files_updated.connect(self.update)
# Buttons
- self.add_button = QtWidgets.QPushButton(strings._('gui_add', True))
- self.add_button.clicked.connect(self.add)
- self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True))
+ if self.common.platform == 'Darwin':
+ # The macOS sandbox makes it so the Mac version needs separate add files
+ # and folders buttons, in order to use native file selection dialogs
+ self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files'))
+ self.add_files_button.clicked.connect(self.add_files)
+ self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder'))
+ self.add_folder_button.clicked.connect(self.add_folder)
+ else:
+ self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
+ self.add_button.clicked.connect(self.add)
+ self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
self.delete_button.clicked.connect(self.delete)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
- button_layout.addWidget(self.add_button)
+ if self.common.platform == 'Darwin':
+ button_layout.addWidget(self.add_files_button)
+ button_layout.addWidget(self.add_folder_button)
+ else:
+ button_layout.addWidget(self.add_button)
button_layout.addWidget(self.delete_button)
# Add the widgets
@@ -309,10 +336,18 @@ class FileSelection(QtWidgets.QVBoxLayout):
"""
# All buttons should be hidden if the server is on
if self.server_on:
- self.add_button.hide()
+ if self.common.platform == 'Darwin':
+ self.add_files_button.hide()
+ self.add_folder_button.hide()
+ else:
+ self.add_button.hide()
self.delete_button.hide()
else:
- self.add_button.show()
+ if self.common.platform == 'Darwin':
+ self.add_files_button.show()
+ self.add_folder_button.show()
+ else:
+ self.add_button.show()
# Delete button should be hidden if item isn't selected
if len(self.file_list.selectedItems()) == 0:
@@ -327,7 +362,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
"""
Add button clicked.
"""
- file_dialog = FileDialog(caption=strings._('gui_choose_items', True))
+ file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items'))
if file_dialog.exec_() == QtWidgets.QDialog.Accepted:
for filename in file_dialog.selectedFiles():
self.file_list.add_file(filename)
@@ -335,6 +370,24 @@ class FileSelection(QtWidgets.QVBoxLayout):
self.file_list.setCurrentItem(None)
self.update()
+ def add_files(self):
+ """
+ Add files button clicked.
+ """
+ files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items'))
+ filenames = files[0]
+ for filename in filenames:
+ self.file_list.add_file(filename)
+
+ def add_folder(self):
+ """
+ Add folder button clicked.
+ """
+ filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent,
+ caption=strings._('gui_choose_items'),
+ options=QtWidgets.QFileDialog.ShowDirsOnly)
+ self.file_list.add_file(filename)
+
def delete(self):
"""
Delete button clicked
@@ -375,22 +428,3 @@ class FileSelection(QtWidgets.QVBoxLayout):
Set the Qt app focus on the file selection box.
"""
self.file_list.setFocus()
-
-class FileDialog(QtWidgets.QFileDialog):
- """
- Overridden version of QFileDialog which allows us to select
- folders as well as, or instead of, files.
- """
- def __init__(self, *args, **kwargs):
- QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
- self.setOption(self.DontUseNativeDialog, True)
- self.setOption(self.ReadOnly, True)
- self.setOption(self.ShowDirsOnly, False)
- self.setFileMode(self.ExistingFiles)
- tree_view = self.findChild(QtWidgets.QTreeView)
- tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
- list_view = self.findChild(QtWidgets.QListView, "listView")
- list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
- def accept(self):
- QtWidgets.QDialog.accept(self)
diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py
new file mode 100644
index 00000000..24e2c242
--- /dev/null
+++ b/onionshare_gui/mode/share_mode/threads.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+from PyQt5 import QtCore
+
+
+class CompressThread(QtCore.QThread):
+ """
+ Compresses files to be shared
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(CompressThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('CompressThread', '__init__')
+
+ # prepare files to share
+ def set_processed_size(self, x):
+ if self.mode._zip_progress_bar != None:
+ self.mode._zip_progress_bar.update_processed_size_signal.emit(x)
+
+ def run(self):
+ self.mode.common.log('CompressThread', 'run')
+
+ try:
+ if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size):
+ self.success.emit()
+ else:
+ # Cancelled
+ pass
+
+ self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames
+ except OSError as e:
+ self.error.emit(e.strerror)
+
+ def cancel(self):
+ self.mode.common.log('CompressThread', 'cancel')
+
+ # Let the Web and ZipWriter objects know that we're canceling compression early
+ self.mode.web.cancel_compression = True
+ try:
+ self.mode.web.zip_writer.cancel_compression = True
+ except AttributeError:
+ # we never made it as far as creating a ZipWriter object
+ pass
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py
index c05e3323..27abf5e5 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,209 +17,171 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-import os, threading, time
+import queue
from PyQt5 import QtCore, QtWidgets, QtGui
-from onionshare import strings, common, web
-from onionshare.settings import Settings
-from onionshare.onion import *
+from onionshare import strings
+from onionshare.web import Web
+
+from .mode.share_mode import ShareMode
+from .mode.receive_mode import ReceiveMode
from .tor_connection_dialog import TorConnectionDialog
from .settings_dialog import SettingsDialog
-from .file_selection import FileSelection
-from .server_status import ServerStatus
-from .downloads import Downloads
-from .alert import Alert
+from .widgets import Alert
from .update_checker import UpdateThread
+from .server_status import ServerStatus
class OnionShareGui(QtWidgets.QMainWindow):
"""
OnionShareGui is the main window for the GUI that contains all of the
GUI elements.
"""
- start_server_finished = QtCore.pyqtSignal()
- stop_server_finished = QtCore.pyqtSignal()
- starting_server_step2 = QtCore.pyqtSignal()
- starting_server_step3 = QtCore.pyqtSignal()
- starting_server_error = QtCore.pyqtSignal(str)
+ MODE_SHARE = 'share'
+ MODE_RECEIVE = 'receive'
- def __init__(self, onion, qtapp, app, filenames, config=False):
+ def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False):
super(OnionShareGui, self).__init__()
- self._initSystemTray()
-
- common.log('OnionShareGui', '__init__')
+ self.common = common
+ self.common.log('OnionShareGui', '__init__')
+ self.setMinimumWidth(820)
+ self.setMinimumHeight(660)
self.onion = onion
self.qtapp = qtapp
self.app = app
+ self.local_only = local_only
+
+ self.mode = self.MODE_SHARE
self.setWindowTitle('OnionShare')
- self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
- self.setMinimumWidth(430)
+ self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
- # Load settings
+ # Load settings, if a custom config was passed in
self.config = config
- self.settings = Settings(self.config)
- self.settings.load()
-
- # File selection
- self.file_selection = FileSelection()
- if filenames:
- for filename in filenames:
- self.file_selection.file_list.add_file(filename)
-
- # Server status
- self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings)
- self.server_status.server_started.connect(self.file_selection.server_started)
- self.server_status.server_started.connect(self.start_server)
- self.server_status.server_started.connect(self.update_server_status_indicator)
- self.server_status.server_stopped.connect(self.file_selection.server_stopped)
- self.server_status.server_stopped.connect(self.stop_server)
- self.server_status.server_stopped.connect(self.update_server_status_indicator)
- self.server_status.server_stopped.connect(self.update_primary_action)
- self.server_status.server_canceled.connect(self.cancel_server)
- self.server_status.server_canceled.connect(self.file_selection.server_stopped)
- self.server_status.server_canceled.connect(self.update_primary_action)
- self.start_server_finished.connect(self.clear_message)
- self.start_server_finished.connect(self.server_status.start_server_finished)
- self.start_server_finished.connect(self.update_server_status_indicator)
- self.stop_server_finished.connect(self.server_status.stop_server_finished)
- self.stop_server_finished.connect(self.update_server_status_indicator)
- self.file_selection.file_list.files_updated.connect(self.server_status.update)
- self.file_selection.file_list.files_updated.connect(self.update_primary_action)
- self.server_status.url_copied.connect(self.copy_url)
- self.server_status.hidservauth_copied.connect(self.copy_hidservauth)
- self.starting_server_step2.connect(self.start_server_step2)
- self.starting_server_step3.connect(self.start_server_step3)
- self.starting_server_error.connect(self.start_server_error)
- self.server_status.button_clicked.connect(self.clear_message)
-
- # Filesize warning
- self.filesize_warning = QtWidgets.QLabel()
- self.filesize_warning.setWordWrap(True)
- self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
- self.filesize_warning.hide()
-
- # Downloads
- self.downloads = Downloads()
- self.downloads_container = QtWidgets.QScrollArea()
- self.downloads_container.setWidget(self.downloads)
- self.downloads_container.setWidgetResizable(True)
- self.downloads_container.setMaximumHeight(200)
- self.downloads_container.setMinimumHeight(75)
- self.vbar = self.downloads_container.verticalScrollBar()
- self.downloads_container.hide() # downloads start out hidden
- self.new_download = False
- self.downloads_in_progress = 0
- self.downloads_completed = 0
-
- # Info label along top of screen
- self.info_layout = QtWidgets.QHBoxLayout()
- self.info_label = QtWidgets.QLabel()
- self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
-
- self.info_in_progress_downloads_count = QtWidgets.QLabel()
- self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
-
- self.info_completed_downloads_count = QtWidgets.QLabel()
- self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
-
- self.update_downloads_completed(self.downloads_in_progress)
- self.update_downloads_in_progress(self.downloads_in_progress)
-
- self.info_layout.addWidget(self.info_label)
- self.info_layout.addStretch()
- self.info_layout.addWidget(self.info_in_progress_downloads_count)
- self.info_layout.addWidget(self.info_completed_downloads_count)
-
- self.info_widget = QtWidgets.QWidget()
- self.info_widget.setLayout(self.info_layout)
- self.info_widget.hide()
-
- # Settings button on the status bar
+ if self.config:
+ self.common.load_settings(self.config)
+
+ # System tray
+ menu = QtWidgets.QMenu()
+ self.settings_action = menu.addAction(strings._('gui_settings_window_title'))
+ self.settings_action.triggered.connect(self.open_settings)
+ help_action = menu.addAction(strings._('gui_settings_button_help'))
+ help_action.triggered.connect(SettingsDialog.help_clicked)
+ exit_action = menu.addAction(strings._('systray_menu_exit'))
+ exit_action.triggered.connect(self.close)
+
+ self.system_tray = QtWidgets.QSystemTrayIcon(self)
+ # The convention is Mac systray icons are always grayscale
+ if self.common.platform == 'Darwin':
+ self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png')))
+ else:
+ self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
+ self.system_tray.setContextMenu(menu)
+ self.system_tray.show()
+
+ # Mode switcher, to switch between share files and receive files
+ self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button'));
+ self.share_mode_button.setFixedHeight(50)
+ self.share_mode_button.clicked.connect(self.share_mode_clicked)
+ self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
+ self.receive_mode_button.setFixedHeight(50)
+ self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False)
- self.settings_button.setFlat(True)
self.settings_button.setFixedWidth(40)
- self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) )
+ self.settings_button.setFixedHeight(50)
+ self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) )
self.settings_button.clicked.connect(self.open_settings)
+ self.settings_button.setStyleSheet(self.common.css['settings_button'])
+ mode_switcher_layout = QtWidgets.QHBoxLayout();
+ mode_switcher_layout.setSpacing(0)
+ mode_switcher_layout.addWidget(self.share_mode_button)
+ mode_switcher_layout.addWidget(self.receive_mode_button)
+ mode_switcher_layout.addWidget(self.settings_button)
# Server status indicator on the status bar
- self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png'))
- self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png'))
- self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png'))
+ self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png'))
+ self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png'))
+ self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png'))
self.server_status_image_label = QtWidgets.QLabel()
self.server_status_image_label.setFixedWidth(20)
- self.server_status_label = QtWidgets.QLabel()
- self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }')
+ self.server_status_label = QtWidgets.QLabel('')
+ self.server_status_label.setStyleSheet(self.common.css['server_status_indicator_label'])
server_status_indicator_layout = QtWidgets.QHBoxLayout()
server_status_indicator_layout.addWidget(self.server_status_image_label)
server_status_indicator_layout.addWidget(self.server_status_label)
self.server_status_indicator = QtWidgets.QWidget()
self.server_status_indicator.setLayout(server_status_indicator_layout)
- self.update_server_status_indicator()
# Status bar
self.status_bar = QtWidgets.QStatusBar()
self.status_bar.setSizeGripEnabled(False)
- statusBar_cssStyleData ="""
- QStatusBar {
- font-style: italic;
- color: #666666;
- }
-
- QStatusBar::item {
- border: 0px;
- }"""
-
- self.status_bar.setStyleSheet(statusBar_cssStyleData)
+ self.status_bar.setStyleSheet(self.common.css['status_bar'])
self.status_bar.addPermanentWidget(self.server_status_indicator)
- self.status_bar.addPermanentWidget(self.settings_button)
self.setStatusBar(self.status_bar)
- # Status bar, zip progress bar
- self._zip_progress_bar = None
- # Status bar, sharing messages
- self.server_share_status_label = QtWidgets.QLabel('')
- self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }')
- self.status_bar.insertWidget(0, self.server_share_status_label)
-
- # Primary action layout
- primary_action_layout = QtWidgets.QVBoxLayout()
- primary_action_layout.addWidget(self.server_status)
- primary_action_layout.addWidget(self.filesize_warning)
- primary_action_layout.addWidget(self.downloads_container)
- self.primary_action = QtWidgets.QWidget()
- self.primary_action.setLayout(primary_action_layout)
- self.primary_action.hide()
- self.update_primary_action()
-
- # Main layout
- self.layout = QtWidgets.QVBoxLayout()
- self.layout.addWidget(self.info_widget)
- self.layout.addLayout(self.file_selection)
- self.layout.addWidget(self.primary_action)
+ # Share mode
+ self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only)
+ self.share_mode.init()
+ self.share_mode.server_status.server_started.connect(self.update_server_status_indicator)
+ self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
+ self.share_mode.start_server_finished.connect(self.update_server_status_indicator)
+ self.share_mode.stop_server_finished.connect(self.update_server_status_indicator)
+ self.share_mode.stop_server_finished.connect(self.stop_server_finished)
+ self.share_mode.start_server_finished.connect(self.clear_message)
+ self.share_mode.server_status.button_clicked.connect(self.clear_message)
+ self.share_mode.server_status.url_copied.connect(self.copy_url)
+ self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
+ self.share_mode.set_server_active.connect(self.set_server_active)
+
+ # Receive mode
+ self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only)
+ self.receive_mode.init()
+ self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator)
+ self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
+ self.receive_mode.start_server_finished.connect(self.update_server_status_indicator)
+ self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator)
+ self.receive_mode.stop_server_finished.connect(self.stop_server_finished)
+ self.receive_mode.start_server_finished.connect(self.clear_message)
+ self.receive_mode.server_status.button_clicked.connect(self.clear_message)
+ self.receive_mode.server_status.url_copied.connect(self.copy_url)
+ self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
+ self.receive_mode.set_server_active.connect(self.set_server_active)
+
+ self.update_mode_switcher()
+ self.update_server_status_indicator()
+
+ # Layouts
+ contents_layout = QtWidgets.QVBoxLayout()
+ contents_layout.setContentsMargins(10, 0, 10, 0)
+ contents_layout.addWidget(self.receive_mode)
+ contents_layout.addWidget(self.share_mode)
+
+ layout = QtWidgets.QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.addLayout(mode_switcher_layout)
+ layout.addLayout(contents_layout)
+
central_widget = QtWidgets.QWidget()
- central_widget.setLayout(self.layout)
+ central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.show()
- # Always start with focus on file selection
- self.file_selection.setFocus()
-
# The server isn't active yet
self.set_server_active(False)
# Create the timer
self.timer = QtCore.QTimer()
- self.timer.timeout.connect(self.check_for_requests)
+ self.timer.timeout.connect(self.timer_callback)
# Start the "Connecting to Tor" dialog, which calls onion.connect()
- tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion)
+ tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion)
tor_con.canceled.connect(self._tor_connection_canceled)
tor_con.open_settings.connect(self._tor_connection_open_settings)
- tor_con.start()
+ if not self.local_only:
+ tor_con.start()
# Start the timer
self.timer.start(500)
@@ -227,77 +189,76 @@ class OnionShareGui(QtWidgets.QMainWindow):
# After connecting to Tor, check for updates
self.check_for_updates()
- def update_primary_action(self):
- # Show or hide primary action layout
- file_count = self.file_selection.file_list.count()
- if file_count > 0:
- self.primary_action.show()
- self.info_widget.show()
-
- # Update the file count in the info label
- total_size_bytes = 0
- for index in range(self.file_selection.file_list.count()):
- item = self.file_selection.file_list.item(index)
- total_size_bytes += item.size_bytes
- total_size_readable = common.human_readable_filesize(total_size_bytes)
-
- if file_count > 1:
- self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable))
- else:
- self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable))
+ def update_mode_switcher(self):
+ # Based on the current mode, switch the mode switcher button styles,
+ # and show and hide widgets to switch modes
+ if self.mode == self.MODE_SHARE:
+ self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
+ self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
+ self.receive_mode.hide()
+ self.share_mode.show()
else:
- self.primary_action.hide()
- self.info_widget.hide()
+ self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
+ self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
- # Resize window
- self.adjustSize()
+ self.share_mode.hide()
+ self.receive_mode.show()
- def update_server_status_indicator(self):
- common.log('OnionShareGui', 'update_server_status_indicator')
+ self.update_server_status_indicator()
- # Set the status image
- if self.server_status.status == self.server_status.STATUS_STOPPED:
- self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
- self.server_status_label.setText(strings._('gui_status_indicator_stopped', True))
- elif self.server_status.status == self.server_status.STATUS_WORKING:
- self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
- self.server_status_label.setText(strings._('gui_status_indicator_working', True))
- elif self.server_status.status == self.server_status.STATUS_STARTED:
- self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
- self.server_status_label.setText(strings._('gui_status_indicator_started', True))
-
- def _initSystemTray(self):
- system = common.get_platform()
+ def share_mode_clicked(self):
+ if self.mode != self.MODE_SHARE:
+ self.common.log('OnionShareGui', 'share_mode_clicked')
+ self.mode = self.MODE_SHARE
+ self.update_mode_switcher()
- menu = QtWidgets.QMenu()
- self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True))
- self.settingsAction.triggered.connect(self.open_settings)
- self.helpAction = menu.addAction(strings._('gui_settings_button_help', True))
- self.helpAction.triggered.connect(SettingsDialog.help_clicked)
- self.exitAction = menu.addAction(strings._('systray_menu_exit', True))
- self.exitAction.triggered.connect(self.close)
-
- self.systemTray = QtWidgets.QSystemTrayIcon(self)
- # The convention is Mac systray icons are always grayscale
- if system == 'Darwin':
- self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png')))
+ def receive_mode_clicked(self):
+ if self.mode != self.MODE_RECEIVE:
+ self.common.log('OnionShareGui', 'receive_mode_clicked')
+ self.mode = self.MODE_RECEIVE
+ self.update_mode_switcher()
+
+ def update_server_status_indicator(self):
+ # Set the status image
+ if self.mode == self.MODE_SHARE:
+ # Share mode
+ if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
+ self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
+ elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
+ self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
+ elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
+ self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
else:
- self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
- self.systemTray.setContextMenu(menu)
- self.systemTray.show()
+ # Receive mode
+ if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
+ self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped'))
+ elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
+ self.server_status_label.setText(strings._('gui_status_indicator_receive_working'))
+ elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED:
+ self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
+ self.server_status_label.setText(strings._('gui_status_indicator_receive_started'))
+
+ def stop_server_finished(self):
+ # When the server stopped, cleanup the ephemeral onion service
+ self.onion.cleanup(stop_tor=False)
def _tor_connection_canceled(self):
"""
If the user cancels before Tor finishes connecting, ask if they want to
quit, or open settings.
"""
- common.log('OnionShareGui', '_tor_connection_canceled')
+ self.common.log('OnionShareGui', '_tor_connection_canceled')
def ask():
- a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
- settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True))
- quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True))
+ a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
+ settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings'))
+ quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit'))
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
a.setDefaultButton(settings_button)
@@ -305,12 +266,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
if a.clickedButton() == settings_button:
# Open settings
- common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
+ self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
self.open_settings()
if a.clickedButton() == quit_button:
# Quit
- common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
+ self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
# Wait 1ms for the event loop to finish, then quit
QtCore.QTimer.singleShot(1, self.qtapp.quit)
@@ -322,7 +283,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
"""
The TorConnectionDialog wants to open the Settings dialog
"""
- common.log('OnionShareGui', '_tor_connection_open_settings')
+ self.common.log('OnionShareGui', '_tor_connection_open_settings')
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
QtCore.QTimer.singleShot(1, self.open_settings)
@@ -331,337 +292,132 @@ class OnionShareGui(QtWidgets.QMainWindow):
"""
Open the SettingsDialog.
"""
- common.log('OnionShareGui', 'open_settings')
+ self.common.log('OnionShareGui', 'open_settings')
def reload_settings():
- common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
- self.settings.load()
+ self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
+ self.common.settings.load()
+
# We might've stopped the main requests timer if a Tor connection failed.
# If we've reloaded settings, we probably succeeded in obtaining a new
# connection. If so, restart the timer.
- if self.onion.is_authenticated():
- if not self.timer.isActive():
- self.timer.start(500)
- # If there were some files listed for sharing, we should be ok to
- # re-enable the 'Start Sharing' button now.
- if self.server_status.file_selection.get_num_files() > 0:
- self.server_status.server_button.setEnabled(True)
- self.status_bar.clearMessage()
+ if not self.local_only:
+ if self.onion.is_authenticated():
+ if not self.timer.isActive():
+ self.timer.start(500)
+ self.share_mode.on_reload_settings()
+ self.receive_mode.on_reload_settings()
+ self.status_bar.clearMessage()
+
# If we switched off the shutdown timeout setting, ensure the widget is hidden.
- if not self.settings.get('shutdown_timeout'):
- self.server_status.shutdown_timeout_container.hide()
+ if not self.common.settings.get('shutdown_timeout'):
+ self.share_mode.server_status.shutdown_timeout_container.hide()
+ self.receive_mode.server_status.shutdown_timeout_container.hide()
- d = SettingsDialog(self.onion, self.qtapp, self.config)
+ d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
d.settings_saved.connect(reload_settings)
d.exec_()
# When settings close, refresh the server status UI
- self.server_status.update()
-
- def start_server(self):
- """
- Start the onionshare server. This uses multiple threads to start the Tor onion
- server and the web app.
- """
- common.log('OnionShareGui', 'start_server')
-
- self.set_server_active(True)
-
- self.app.set_stealth(self.settings.get('use_stealth'))
-
- # Hide and reset the downloads if we have previously shared
- self.downloads_container.hide()
- self.downloads.reset_downloads()
- self.reset_info_counters()
- self.status_bar.clearMessage()
- self.server_share_status_label.setText('')
-
- # Reset web counters
- web.download_count = 0
- web.error404_count = 0
- web.set_gui_mode()
-
- # start the onion service in a new thread
- def start_onion_service(self):
- try:
- self.app.start_onion_service()
- self.starting_server_step2.emit()
-
- except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
- self.starting_server_error.emit(e.args[0])
- return
-
-
- self.app.stay_open = not self.settings.get('close_after_first_download')
-
- # start onionshare http service in new thread
- t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug')))
- t.daemon = True
- t.start()
- # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
- time.sleep(0.2)
-
- common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
- self.t = OnionThread(function=start_onion_service, kwargs={'self': self})
- self.t.daemon = True
- self.t.start()
-
- def start_server_step2(self):
- """
- Step 2 in starting the onionshare server. Zipping up files.
- """
- common.log('OnionShareGui', 'start_server_step2')
-
- # add progress bar to the status bar, indicating the crunching of files.
- self._zip_progress_bar = ZipProgressBar(0)
- self.filenames = []
- for index in range(self.file_selection.file_list.count()):
- self.filenames.append(self.file_selection.file_list.item(index).filename)
-
- self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames)
- self.status_bar.insertWidget(0, self._zip_progress_bar)
-
- # prepare the files for sending in a new thread
- def finish_starting_server(self):
- # prepare files to share
- def _set_processed_size(x):
- if self._zip_progress_bar != None:
- self._zip_progress_bar.update_processed_size_signal.emit(x)
- try:
- web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
- self.app.cleanup_filenames.append(web.zip_filename)
- self.starting_server_step3.emit()
-
- # done
- self.start_server_finished.emit()
- except OSError as e:
- self.starting_server_error.emit(e.strerror)
- return
-
- t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
- t.daemon = True
- t.start()
-
- def start_server_step3(self):
- """
- Step 3 in starting the onionshare server. This displays the large filesize
- warning, if applicable.
- """
- common.log('OnionShareGui', 'start_server_step3')
-
- # Remove zip progress bar
- if self._zip_progress_bar is not None:
- self.status_bar.removeWidget(self._zip_progress_bar)
- self._zip_progress_bar = None
-
- # warn about sending large files over Tor
- if web.zip_filesize >= 157286400: # 150mb
- self.filesize_warning.setText(strings._("large_filesize", True))
- self.filesize_warning.show()
-
- if self.settings.get('shutdown_timeout'):
- # Convert the date value to seconds between now and then
- now = QtCore.QDateTime.currentDateTime()
- self.timeout = now.secsTo(self.server_status.timeout)
- # Set the shutdown timeout value
- if self.timeout > 0:
- self.app.shutdown_timer = common.close_after_seconds(self.timeout)
- self.app.shutdown_timer.start()
- # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
- else:
- self.stop_server()
- self.start_server_error(strings._('gui_server_started_after_timeout'))
-
- def start_server_error(self, error):
- """
- If there's an error when trying to start the onion service
- """
- common.log('OnionShareGui', 'start_server_error')
-
- self.set_server_active(False)
-
- Alert(error, QtWidgets.QMessageBox.Warning)
- self.server_status.stop_server()
- if self._zip_progress_bar is not None:
- self.status_bar.removeWidget(self._zip_progress_bar)
- self._zip_progress_bar = None
- self.status_bar.clearMessage()
-
- def cancel_server(self):
- """
- Cancel the server while it is preparing to start
- """
- if self.t:
- self.t.quit()
- self.stop_server()
-
- def stop_server(self):
- """
- Stop the onionshare server.
- """
- common.log('OnionShareGui', 'stop_server')
-
- if self.server_status.status != self.server_status.STATUS_STOPPED:
- try:
- web.stop(self.app.port)
- except:
- # Probably we had no port to begin with (Onion service didn't start)
- pass
- self.app.cleanup()
- # Remove ephemeral service, but don't disconnect from Tor
- self.onion.cleanup(stop_tor=False)
- self.filesize_warning.hide()
- self.downloads_in_progress = 0
- self.downloads_completed = 0
- self.update_downloads_in_progress(0)
- self.file_selection.file_list.adjustSize()
-
- self.set_server_active(False)
- self.stop_server_finished.emit()
+ self.share_mode.server_status.update()
+ self.receive_mode.server_status.update()
def check_for_updates(self):
"""
Check for updates in a new thread, if enabled.
"""
- system = common.get_platform()
- if system == 'Windows' or system == 'Darwin':
- if self.settings.get('use_autoupdate'):
+ if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
+ if self.common.settings.get('use_autoupdate'):
def update_available(update_url, installed_version, latest_version):
- Alert(strings._("update_available", True).format(update_url, installed_version, latest_version))
+ Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version))
- self.update_thread = UpdateThread(self.onion, self.config)
+ self.update_thread = UpdateThread(self.common, self.onion, self.config)
self.update_thread.update_available.connect(update_available)
self.update_thread.start()
- @staticmethod
- def _compute_total_size(filenames):
- total_size = 0
- for filename in filenames:
- if os.path.isfile(filename):
- total_size += os.path.getsize(filename)
- if os.path.isdir(filename):
- total_size += common.dir_size(filename)
- return total_size
-
- def check_for_requests(self):
+ def timer_callback(self):
"""
- Check for messages communicated from the web app, and update the GUI accordingly.
+ Check for messages communicated from the web app, and update the GUI accordingly. Also,
+ call ShareMode and ReceiveMode's timer_callbacks.
"""
self.update()
- # Have we lost connection to Tor somehow?
- if not self.onion.is_authenticated():
- self.timer.stop()
- if self.server_status.status != self.server_status.STATUS_STOPPED:
- self.server_status.stop_server()
- self.server_status.server_button.setEnabled(False)
- self.status_bar.showMessage(strings._('gui_tor_connection_lost', True))
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True))
-
- # scroll to the bottom of the dl progress bar log pane
- # if a new download has been added
- if self.new_download:
- self.vbar.setValue(self.vbar.maximum())
- self.new_download = False
+ if not self.local_only:
+ # Have we lost connection to Tor somehow?
+ if not self.onion.is_authenticated():
+ self.timer.stop()
+ self.status_bar.showMessage(strings._('gui_tor_connection_lost'))
+ self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings'))
+
+ self.share_mode.handle_tor_broke()
+ self.receive_mode.handle_tor_broke()
+
+ # Process events from the web object
+ if self.mode == self.MODE_SHARE:
+ mode = self.share_mode
+ else:
+ mode = self.receive_mode
events = []
done = False
while not done:
try:
- r = web.q.get(False)
+ r = mode.web.q.get(False)
events.append(r)
- except web.queue.Empty:
+ except queue.Empty:
done = True
for event in events:
- if event["type"] == web.REQUEST_LOAD:
- self.status_bar.showMessage(strings._('download_page_loaded', True))
-
- elif event["type"] == web.REQUEST_DOWNLOAD:
- self.downloads_container.show() # show the downloads layout
- self.downloads.add_download(event["data"]["id"], web.zip_filesize)
- self.new_download = True
- self.downloads_in_progress += 1
- self.update_downloads_in_progress(self.downloads_in_progress)
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
-
- elif event["type"] == web.REQUEST_RATE_LIMIT:
- self.stop_server()
- Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
-
- elif event["type"] == web.REQUEST_PROGRESS:
- self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
-
- # is the download complete?
- if event["data"]["bytes"] == web.zip_filesize:
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
- # Update the total 'completed downloads' info
- self.downloads_completed += 1
- self.update_downloads_completed(self.downloads_completed)
- # Update the 'in progress downloads' info
- self.downloads_in_progress -= 1
- self.update_downloads_in_progress(self.downloads_in_progress)
-
- # close on finish?
- if not web.get_stay_open():
- self.server_status.stop_server()
- self.status_bar.clearMessage()
- self.server_share_status_label.setText(strings._('closing_automatically', True))
- else:
- if self.server_status.status == self.server_status.STATUS_STOPPED:
- self.downloads.cancel_download(event["data"]["id"])
- self.downloads_in_progress = 0
- self.update_downloads_in_progress(self.downloads_in_progress)
-
-
- elif event["type"] == web.REQUEST_CANCELED:
- self.downloads.cancel_download(event["data"]["id"])
- # Update the 'in progress downloads' info
- self.downloads_in_progress -= 1
- self.update_downloads_in_progress(self.downloads_in_progress)
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
-
- elif event["path"] != '/favicon.ico':
- self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"]))
-
- # If the auto-shutdown timer has stopped, stop the server
- if self.server_status.status == self.server_status.STATUS_STARTED:
- if self.app.shutdown_timer and self.settings.get('shutdown_timeout'):
- if self.timeout > 0:
- now = QtCore.QDateTime.currentDateTime()
- seconds_remaining = now.secsTo(self.server_status.timeout)
- self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining))
- if not self.app.shutdown_timer.is_alive():
- # If there were no attempts to download the share, or all downloads are done, we can stop
- if web.download_count == 0 or web.done:
- self.server_status.stop_server()
- self.status_bar.clearMessage()
- self.server_share_status_label.setText(strings._('close_on_timeout', True))
- # A download is probably still running - hold off on stopping the share
- else:
- self.status_bar.clearMessage()
- self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
+ if event["type"] == Web.REQUEST_LOAD:
+ mode.handle_request_load(event)
+
+ elif event["type"] == Web.REQUEST_STARTED:
+ mode.handle_request_started(event)
+
+ elif event["type"] == Web.REQUEST_RATE_LIMIT:
+ mode.handle_request_rate_limit(event)
+
+ elif event["type"] == Web.REQUEST_PROGRESS:
+ mode.handle_request_progress(event)
+
+ elif event["type"] == Web.REQUEST_CANCELED:
+ mode.handle_request_canceled(event)
+
+ elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED:
+ mode.handle_request_upload_file_renamed(event)
+
+ elif event["type"] == Web.REQUEST_UPLOAD_SET_DIR:
+ mode.handle_request_upload_set_dir(event)
+
+ elif event["type"] == Web.REQUEST_UPLOAD_FINISHED:
+ mode.handle_request_upload_finished(event)
+
+ elif event["type"] == Web.REQUEST_UPLOAD_CANCELED:
+ mode.handle_request_upload_canceled(event)
+
+ if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE:
+ Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))
+
+ if event["type"] == Web.REQUEST_OTHER:
+ if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug):
+ self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"]))
+
+ mode.timer_callback()
def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
- common.log('OnionShareGui', 'copy_url')
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
+ self.common.log('OnionShareGui', 'copy_url')
+ self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url'))
def copy_hidservauth(self):
"""
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
"""
- common.log('OnionShareGui', 'copy_hidservauth')
- if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
- self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
+ self.common.log('OnionShareGui', 'copy_hidservauth')
+ self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth'))
def clear_message(self):
"""
@@ -671,56 +427,42 @@ class OnionShareGui(QtWidgets.QMainWindow):
def set_server_active(self, active):
"""
- Disable the Settings button while an OnionShare server is active.
+ Disable the Settings and Receive Files buttons while an Share Files server is active.
"""
if active:
self.settings_button.hide()
+ if self.mode == self.MODE_SHARE:
+ self.share_mode_button.show()
+ self.receive_mode_button.hide()
+ else:
+ self.share_mode_button.hide()
+ self.receive_mode_button.show()
else:
self.settings_button.show()
+ self.share_mode_button.show()
+ self.receive_mode_button.show()
# Disable settings menu action when server is active
- self.settingsAction.setEnabled(not active)
-
- def reset_info_counters(self):
- """
- Set the info counters back to zero.
- """
- self.update_downloads_completed(0)
- self.update_downloads_in_progress(0)
-
- def update_downloads_completed(self, count):
- """
- Update the 'Downloads completed' info widget.
- """
- if count == 0:
- self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png')
- else:
- self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png')
- self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count))
- self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
-
- def update_downloads_in_progress(self, count):
- """
- Update the 'Downloads in progress' info widget.
- """
- if count == 0:
- self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png')
- else:
- self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png')
- self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count))
- self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
+ self.settings_action.setEnabled(not active)
def closeEvent(self, e):
- common.log('OnionShareGui', 'closeEvent')
+ self.common.log('OnionShareGui', 'closeEvent')
try:
- if self.server_status.status != self.server_status.STATUS_STOPPED:
- common.log('OnionShareGui', 'closeEvent, opening warning dialog')
+ if self.mode == OnionShareGui.MODE_SHARE:
+ server_status = self.share_mode.server_status
+ else:
+ server_status = self.receive_mode.server_status
+ if server_status.status != server_status.STATUS_STOPPED:
+ self.common.log('OnionShareGui', 'closeEvent, opening warning dialog')
dialog = QtWidgets.QMessageBox()
- dialog.setWindowTitle(strings._('gui_quit_title', True))
- dialog.setText(strings._('gui_quit_warning', True))
+ dialog.setWindowTitle(strings._('gui_quit_title'))
+ if self.mode == OnionShareGui.MODE_SHARE:
+ dialog.setText(strings._('gui_share_quit_warning'))
+ else:
+ dialog.setText(strings._('gui_receive_quit_warning'))
dialog.setIcon(QtWidgets.QMessageBox.Critical)
- quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole)
- dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole)
+ quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole)
+ dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole)
dialog.setDefaultButton(dont_quit_button)
reply = dialog.exec_()
@@ -734,81 +476,3 @@ class OnionShareGui(QtWidgets.QMainWindow):
except:
e.accept()
-
-
-class ZipProgressBar(QtWidgets.QProgressBar):
- update_processed_size_signal = QtCore.pyqtSignal(int)
-
- def __init__(self, total_files_size):
- super(ZipProgressBar, self).__init__()
- self.setMaximumHeight(20)
- self.setMinimumWidth(200)
- self.setValue(0)
- self.setFormat(strings._('zip_progress_bar_format'))
- cssStyleData ="""
- QProgressBar {
- border: 1px solid #4e064f;
- background-color: #ffffff !important;
- text-align: center;
- color: #9b9b9b;
- }
-
- QProgressBar::chunk {
- border: 0px;
- background-color: #4e064f;
- width: 10px;
- }"""
- self.setStyleSheet(cssStyleData)
-
- self._total_files_size = total_files_size
- self._processed_size = 0
-
- self.update_processed_size_signal.connect(self.update_processed_size)
-
- @property
- def total_files_size(self):
- return self._total_files_size
-
- @total_files_size.setter
- def total_files_size(self, val):
- self._total_files_size = val
-
- @property
- def processed_size(self):
- return self._processed_size
-
- @processed_size.setter
- def processed_size(self, val):
- self.update_processed_size(val)
-
- def update_processed_size(self, val):
- self._processed_size = val
- if self.processed_size < self.total_files_size:
- self.setValue(int((self.processed_size * 100) / self.total_files_size))
- elif self.total_files_size != 0:
- self.setValue(100)
- else:
- self.setValue(0)
-
-
-class OnionThread(QtCore.QThread):
- """
- A QThread for starting our Onion Service.
- By using QThread rather than threading.Thread, we are able
- to call quit() or terminate() on the startup if the user
- decided to cancel (in which case do not proceed with obtaining
- the Onion address and starting the web server).
- """
- def __init__(self, function, kwargs=None):
- super(OnionThread, self).__init__()
- common.log('OnionThread', '__init__')
- self.function = function
- if not kwargs:
- self.kwargs = {}
- else:
- self.kwargs = kwargs
-
- def run(self):
- common.log('OnionThread', 'run')
-
- self.function(**self.kwargs)
diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py
index f3156fed..e34a3d16 100644
--- a/onionshare_gui/server_status.py
+++ b/onionshare_gui/server_status.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,45 +18,61 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import platform
-from .alert import Alert
+import textwrap
from PyQt5 import QtCore, QtWidgets, QtGui
-from onionshare import strings, common, settings
+from onionshare import strings
+
+from .widgets import Alert
class ServerStatus(QtWidgets.QWidget):
"""
The server status chunk of the GUI.
"""
server_started = QtCore.pyqtSignal()
+ server_started_finished = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal()
server_canceled = QtCore.pyqtSignal()
button_clicked = QtCore.pyqtSignal()
url_copied = QtCore.pyqtSignal()
hidservauth_copied = QtCore.pyqtSignal()
+ MODE_SHARE = 'share'
+ MODE_RECEIVE = 'receive'
+
STATUS_STOPPED = 0
STATUS_WORKING = 1
STATUS_STARTED = 2
- def __init__(self, qtapp, app, web, file_selection, settings):
+ def __init__(self, common, qtapp, app, file_selection=None, local_only=False):
super(ServerStatus, self).__init__()
+
+ self.common = common
+
self.status = self.STATUS_STOPPED
+ self.mode = None # Gets set in self.set_mode
self.qtapp = qtapp
self.app = app
- self.web = web
- self.file_selection = file_selection
- self.settings = settings
+ self.web = None
+ self.local_only = local_only
+
+ self.resizeEvent(None)
# Shutdown timeout layout
- self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
+ self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout'))
self.shutdown_timeout = QtWidgets.QDateTimeEdit()
- # Set proposed timeout to be 5 minutes into the future
self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
- self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now
- self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ if self.local_only:
+ # For testing
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime())
+ else:
+ # Set proposed timeout to be 5 minutes into the future
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
shutdown_timeout_layout = QtWidgets.QHBoxLayout()
shutdown_timeout_layout.addWidget(self.shutdown_timeout_label)
@@ -69,31 +85,29 @@ class ServerStatus(QtWidgets.QWidget):
self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout)
self.shutdown_timeout_container.hide()
-
# Server layout
self.server_button = QtWidgets.QPushButton()
self.server_button.clicked.connect(self.server_button_clicked)
# URL layout
- url_font = QtGui.QFont()
- self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True))
+ url_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
+ self.url_description = QtWidgets.QLabel()
self.url_description.setWordWrap(True)
self.url_description.setMinimumHeight(50)
self.url = QtWidgets.QLabel()
self.url.setFont(url_font)
self.url.setWordWrap(True)
- self.url.setMinimumHeight(60)
self.url.setMinimumSize(self.url.sizeHint())
- self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }')
+ self.url.setStyleSheet(self.common.css['server_status_url'])
- url_buttons_style = 'QPushButton { color: #3f7fcf; }'
- self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True))
+ self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url'))
self.copy_url_button.setFlat(True)
- self.copy_url_button.setStyleSheet(url_buttons_style)
+ self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons'])
+ self.copy_url_button.setMinimumHeight(65)
self.copy_url_button.clicked.connect(self.copy_url)
- self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
+ self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth'))
self.copy_hidservauth_button.setFlat(True)
- self.copy_hidservauth_button.setStyleSheet(url_buttons_style)
+ self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons'])
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
url_buttons_layout = QtWidgets.QHBoxLayout()
url_buttons_layout.addWidget(self.copy_url_button)
@@ -112,14 +126,42 @@ class ServerStatus(QtWidgets.QWidget):
layout.addWidget(self.shutdown_timeout_container)
self.setLayout(layout)
+ def set_mode(self, share_mode, file_selection=None):
+ """
+ The server status is in share mode.
+ """
+ self.mode = share_mode
+
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.file_selection = file_selection
+
self.update()
+ def resizeEvent(self, event):
+ """
+ When the widget is resized, try and adjust the display of a v3 onion URL.
+ """
+ try:
+ # Wrap the URL label
+ url_length=len(self.get_url())
+ if url_length > 60:
+ width = self.frameGeometry().width()
+ if width < 530:
+ wrapped_onion_url = textwrap.fill(self.get_url(), 46)
+ self.url.setText(wrapped_onion_url)
+ else:
+ self.url.setText(self.get_url())
+ except:
+ pass
+
+
def shutdown_timeout_reset(self):
"""
Reset the timeout in the UI after stopping a share
"""
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ if not self.local_only:
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
def update(self):
"""
@@ -129,31 +171,36 @@ class ServerStatus(QtWidgets.QWidget):
if self.status == self.STATUS_STARTED:
self.url_description.show()
- info_image = common.get_resource_path('images/info.png')
- self.url_description.setText(strings._('gui_url_description', True).format(info_image))
+ info_image = self.common.get_resource_path('images/info.png')
+
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.url_description.setText(strings._('gui_share_url_description').format(info_image))
+ else:
+ self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
+
# Show a Tool Tip explaining the lifecycle of this URL
- if self.settings.get('save_private_key'):
- if self.settings.get('close_after_first_download'):
- self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True))
+ if self.common.settings.get('save_private_key'):
+ if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'):
+ self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent'))
else:
- self.url_description.setToolTip(strings._('gui_url_label_persistent', True))
+ self.url_description.setToolTip(strings._('gui_url_label_persistent'))
else:
- if self.settings.get('close_after_first_download'):
- self.url_description.setToolTip(strings._('gui_url_label_onetime', True))
+ if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'):
+ self.url_description.setToolTip(strings._('gui_url_label_onetime'))
else:
- self.url_description.setToolTip(strings._('gui_url_label_stay_open', True))
+ self.url_description.setToolTip(strings._('gui_url_label_stay_open'))
- self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug))
+ self.url.setText(self.get_url())
self.url.show()
self.copy_url_button.show()
- if self.settings.get('save_private_key'):
- if not self.settings.get('slug'):
- self.settings.set('slug', self.web.slug)
- self.settings.save()
+ if self.common.settings.get('save_private_key'):
+ if not self.common.settings.get('slug'):
+ self.common.settings.set('slug', self.web.slug)
+ self.common.settings.save()
- if self.settings.get('shutdown_timeout'):
+ if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.hide()
if self.app.stealth:
@@ -167,39 +214,46 @@ class ServerStatus(QtWidgets.QWidget):
self.copy_hidservauth_button.hide()
# Button
- button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }'
- button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }'
- button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }'
- if self.file_selection.get_num_files() == 0:
+ if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0:
self.server_button.hide()
else:
self.server_button.show()
if self.status == self.STATUS_STOPPED:
- self.server_button.setStyleSheet(button_stopped_style)
+ self.server_button.setStyleSheet(self.common.css['server_status_button_stopped'])
self.server_button.setEnabled(True)
- self.server_button.setText(strings._('gui_start_server', True))
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.server_button.setText(strings._('gui_share_start_server'))
+ else:
+ self.server_button.setText(strings._('gui_receive_start_server'))
self.server_button.setToolTip('')
- if self.settings.get('shutdown_timeout'):
+ if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.show()
elif self.status == self.STATUS_STARTED:
- self.server_button.setStyleSheet(button_started_style)
+ self.server_button.setStyleSheet(self.common.css['server_status_button_started'])
self.server_button.setEnabled(True)
- self.server_button.setText(strings._('gui_stop_server', True))
- if self.settings.get('shutdown_timeout'):
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.server_button.setText(strings._('gui_share_stop_server'))
+ else:
+ self.server_button.setText(strings._('gui_receive_stop_server'))
+ if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.hide()
- self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout))
+ if self.mode == ServerStatus.MODE_SHARE:
+ self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout))
+ else:
+ self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout))
+
elif self.status == self.STATUS_WORKING:
- self.server_button.setStyleSheet(button_working_style)
+ self.server_button.setStyleSheet(self.common.css['server_status_button_working'])
self.server_button.setEnabled(True)
self.server_button.setText(strings._('gui_please_wait'))
- if self.settings.get('shutdown_timeout'):
+ if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.hide()
else:
- self.server_button.setStyleSheet(button_working_style)
+ self.server_button.setStyleSheet(self.common.css['server_status_button_working'])
self.server_button.setEnabled(False)
self.server_button.setText(strings._('gui_please_wait'))
- if self.settings.get('shutdown_timeout'):
+ if self.common.settings.get('shutdown_timeout'):
self.shutdown_timeout_container.hide()
def server_button_clicked(self):
@@ -207,12 +261,15 @@ class ServerStatus(QtWidgets.QWidget):
Toggle starting or stopping the server.
"""
if self.status == self.STATUS_STOPPED:
- if self.settings.get('shutdown_timeout'):
- # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
- self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
+ if self.common.settings.get('shutdown_timeout'):
+ if self.local_only:
+ self.timeout = self.shutdown_timeout.dateTime().toPyDateTime()
+ else:
+ # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
+ self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
# If the timeout has actually passed already before the user hit Start, refuse to start the server.
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout:
- Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
+ Alert(self.common, strings._('gui_server_timeout_expired'), QtWidgets.QMessageBox.Warning)
else:
self.start_server()
else:
@@ -238,6 +295,7 @@ class ServerStatus(QtWidgets.QWidget):
self.status = self.STATUS_STARTED
self.copy_url()
self.update()
+ self.server_started_finished.emit()
def stop_server(self):
"""
@@ -252,7 +310,7 @@ class ServerStatus(QtWidgets.QWidget):
"""
Cancel the server.
"""
- common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
+ self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
self.status = self.STATUS_WORKING
self.shutdown_timeout_reset()
self.update()
@@ -269,10 +327,8 @@ class ServerStatus(QtWidgets.QWidget):
"""
Copy the onionshare URL to the clipboard.
"""
- url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
-
clipboard = self.qtapp.clipboard()
- clipboard.setText(url)
+ clipboard.setText(self.get_url())
self.url_copied.emit()
@@ -284,3 +340,13 @@ class ServerStatus(QtWidgets.QWidget):
clipboard.setText(self.app.auth_string)
self.hidservauth_copied.emit()
+
+ def get_url(self):
+ """
+ Returns the OnionShare URL.
+ """
+ if self.common.settings.get('public_mode'):
+ url = 'http://{0:s}'.format(self.app.onion_host)
+ else:
+ url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
+ return url
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index c2dca97c..2933784c 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -24,7 +24,7 @@ from onionshare import strings, common
from onionshare.settings import Settings
from onionshare.onion import *
-from .alert import Alert
+from .widgets import Alert
from .update_checker import *
from .tor_connection_dialog import TorConnectionDialog
@@ -34,93 +34,194 @@ class SettingsDialog(QtWidgets.QDialog):
"""
settings_saved = QtCore.pyqtSignal()
- def __init__(self, onion, qtapp, config=False):
+ def __init__(self, common, onion, qtapp, config=False, local_only=False):
super(SettingsDialog, self).__init__()
- common.log('SettingsDialog', '__init__')
+
+ self.common = common
+
+ self.common.log('SettingsDialog', '__init__')
self.onion = onion
self.qtapp = qtapp
self.config = config
+ self.local_only = local_only
self.setModal(True)
- self.setWindowTitle(strings._('gui_settings_window_title', True))
- self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
+ self.setWindowTitle(strings._('gui_settings_window_title'))
+ self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.system = platform.system()
- # Sharing options
-
- # Close after first download
- self.close_after_first_download_checkbox = QtWidgets.QCheckBox()
- self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
- self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True))
-
- # Whether or not to show systray notifications
- self.systray_notifications_checkbox = QtWidgets.QCheckBox()
- self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked)
- self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True))
-
- # Whether or not to use a shutdown timer
+ # General settings
+
+ # Use a slug or not ('public mode')
+ self.public_mode_checkbox = QtWidgets.QCheckBox()
+ self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox"))
+ public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode"))
+ public_mode_label.setStyleSheet(self.common.css['settings_whats_this'])
+ public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ public_mode_label.setOpenExternalLinks(True)
+ public_mode_label.setMinimumSize(public_mode_label.sizeHint())
+ public_mode_layout = QtWidgets.QHBoxLayout()
+ public_mode_layout.addWidget(self.public_mode_checkbox)
+ public_mode_layout.addWidget(public_mode_label)
+ public_mode_layout.addStretch()
+ public_mode_layout.setContentsMargins(0,0,0,0)
+ self.public_mode_widget = QtWidgets.QWidget()
+ self.public_mode_widget.setLayout(public_mode_layout)
+
+ # Whether or not to use a shutdown ('auto-stop') timer
self.shutdown_timeout_checkbox = QtWidgets.QCheckBox()
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
- self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True))
-
- # Whether or not to save the Onion private key for reuse
+ self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox"))
+ shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer"))
+ shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this'])
+ shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ shutdown_timeout_label.setOpenExternalLinks(True)
+ shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint())
+ shutdown_timeout_layout = QtWidgets.QHBoxLayout()
+ shutdown_timeout_layout.addWidget(self.shutdown_timeout_checkbox)
+ shutdown_timeout_layout.addWidget(shutdown_timeout_label)
+ shutdown_timeout_layout.addStretch()
+ shutdown_timeout_layout.setContentsMargins(0,0,0,0)
+ self.shutdown_timeout_widget = QtWidgets.QWidget()
+ self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout)
+
+ # General settings layout
+ general_group_layout = QtWidgets.QVBoxLayout()
+ general_group_layout.addWidget(self.public_mode_widget)
+ general_group_layout.addWidget(self.shutdown_timeout_widget)
+ general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label"))
+ general_group.setLayout(general_group_layout)
+
+ # Onion settings
+
+ # Label telling user to connect to Tor for onion service settings
+ self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings"))
+ self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor'])
+
+ # Whether or not to save the Onion private key for reuse (persistent URL mode)
self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
- self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
-
- # Sharing options layout
- sharing_group_layout = QtWidgets.QVBoxLayout()
- sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
- sharing_group_layout.addWidget(self.systray_notifications_checkbox)
- sharing_group_layout.addWidget(self.shutdown_timeout_checkbox)
- sharing_group_layout.addWidget(self.save_private_key_checkbox)
- sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True))
- sharing_group.setLayout(sharing_group_layout)
-
- # Stealth options
+ self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox"))
+ save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL"))
+ save_private_key_label.setStyleSheet(self.common.css['settings_whats_this'])
+ save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ save_private_key_label.setOpenExternalLinks(True)
+ save_private_key_layout = QtWidgets.QHBoxLayout()
+ save_private_key_layout.addWidget(self.save_private_key_checkbox)
+ save_private_key_layout.addWidget(save_private_key_label)
+ save_private_key_layout.addStretch()
+ save_private_key_layout.setContentsMargins(0,0,0,0)
+ self.save_private_key_widget = QtWidgets.QWidget()
+ self.save_private_key_widget.setLayout(save_private_key_layout)
+
+ # Whether or not to use legacy v2 onions
+ self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox()
+ self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox"))
+ self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked)
+ use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses"))
+ use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this'])
+ use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ use_legacy_v2_onions_label.setOpenExternalLinks(True)
+ use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout()
+ use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox)
+ use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label)
+ use_legacy_v2_onions_layout.addStretch()
+ use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0)
+ self.use_legacy_v2_onions_widget = QtWidgets.QWidget()
+ self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout)
# Stealth
- stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True))
- stealth_details.setWordWrap(True)
- stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
- stealth_details.setOpenExternalLinks(True)
- stealth_details.setMinimumSize(stealth_details.sizeHint())
self.stealth_checkbox = QtWidgets.QCheckBox()
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
- self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
-
- hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
- hidservauth_details.setWordWrap(True)
- hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
- hidservauth_details.hide()
-
- self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
+ self.stealth_checkbox.setText(strings._("gui_settings_stealth_option"))
+ self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect)
+ use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services"))
+ use_stealth_label.setStyleSheet(self.common.css['settings_whats_this'])
+ use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ use_stealth_label.setOpenExternalLinks(True)
+ use_stealth_label.setMinimumSize(use_stealth_label.sizeHint())
+ use_stealth_layout = QtWidgets.QHBoxLayout()
+ use_stealth_layout.addWidget(self.stealth_checkbox)
+ use_stealth_layout.addWidget(use_stealth_label)
+ use_stealth_layout.addStretch()
+ use_stealth_layout.setContentsMargins(0,0,0,0)
+ self.use_stealth_widget = QtWidgets.QWidget()
+ self.use_stealth_widget.setLayout(use_stealth_layout)
+
+ self.hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string'))
+ self.hidservauth_details.setWordWrap(True)
+ self.hidservauth_details.setMinimumSize(self.hidservauth_details.sizeHint())
+ self.hidservauth_details.hide()
+
+ self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth'))
self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked)
self.hidservauth_copy_button.hide()
- # Stealth options layout
- stealth_group_layout = QtWidgets.QVBoxLayout()
- stealth_group_layout.addWidget(stealth_details)
- stealth_group_layout.addWidget(self.stealth_checkbox)
- stealth_group_layout.addWidget(hidservauth_details)
- stealth_group_layout.addWidget(self.hidservauth_copy_button)
- stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True))
- stealth_group.setLayout(stealth_group_layout)
+ # Onion settings widget
+ onion_settings_layout = QtWidgets.QVBoxLayout()
+ onion_settings_layout.setContentsMargins(0, 0, 0, 0)
+ onion_settings_layout.addWidget(self.save_private_key_widget)
+ onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget)
+ onion_settings_layout.addWidget(self.use_stealth_widget)
+ onion_settings_layout.addWidget(self.hidservauth_details)
+ onion_settings_layout.addWidget(self.hidservauth_copy_button)
+ self.onion_settings_widget = QtWidgets.QWidget()
+ self.onion_settings_widget.setLayout(onion_settings_layout)
+
+ # Onion settings layout
+ onion_group_layout = QtWidgets.QVBoxLayout()
+ onion_group_layout.addWidget(self.connect_to_tor_label)
+ onion_group_layout.addWidget(self.onion_settings_widget)
+ onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label"))
+ onion_group.setLayout(onion_group_layout)
+
+
+ # Sharing options
+
+ # Close after first download
+ self.close_after_first_download_checkbox = QtWidgets.QCheckBox()
+ self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
+ self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option"))
+
+ # Sharing options layout
+ sharing_group_layout = QtWidgets.QVBoxLayout()
+ sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
+ sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label"))
+ sharing_group.setLayout(sharing_group_layout)
+
+ # OnionShare data dir
+ data_dir_label = QtWidgets.QLabel(strings._('gui_settings_data_dir_label'));
+ self.data_dir_lineedit = QtWidgets.QLineEdit()
+ self.data_dir_lineedit.setReadOnly(True)
+ data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button'))
+ data_dir_button.clicked.connect(self.data_dir_button_clicked)
+ data_dir_layout = QtWidgets.QHBoxLayout()
+ data_dir_layout.addWidget(data_dir_label)
+ data_dir_layout.addWidget(self.data_dir_lineedit)
+ data_dir_layout.addWidget(data_dir_button)
+
+ # Receiving options layout
+ receiving_group_layout = QtWidgets.QVBoxLayout()
+ receiving_group_layout.addLayout(data_dir_layout)
+ receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label"))
+ receiving_group.setLayout(receiving_group_layout)
# Automatic updates options
# Autoupdate
self.autoupdate_checkbox = QtWidgets.QCheckBox()
self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked)
- self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option", True))
+ self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option"))
# Last update time
self.autoupdate_timestamp = QtWidgets.QLabel()
# Check for updates button
- self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button', True))
+ self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button'))
self.check_for_updates_button.clicked.connect(self.check_for_updates)
# We can't check for updates if not connected to Tor
if not self.onion.connected_to_tor:
@@ -131,17 +232,32 @@ class SettingsDialog(QtWidgets.QDialog):
autoupdate_group_layout.addWidget(self.autoupdate_checkbox)
autoupdate_group_layout.addWidget(self.autoupdate_timestamp)
autoupdate_group_layout.addWidget(self.check_for_updates_button)
- autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label", True))
+ autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label"))
autoupdate_group.setLayout(autoupdate_group_layout)
# Autoupdate is only available for Windows and Mac (Linux updates using package manager)
if self.system != 'Windows' and self.system != 'Darwin':
autoupdate_group.hide()
+ # Language settings
+ language_label = QtWidgets.QLabel(strings._("gui_settings_language_label"))
+ self.language_combobox = QtWidgets.QComboBox()
+ # Populate the dropdown with all of OnionShare's available languages
+ language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()}
+ language_names = list(language_names_to_locales)
+ language_names.sort()
+ for language_name in language_names:
+ locale = language_names_to_locales[language_name]
+ self.language_combobox.addItem(language_name, QtCore.QVariant(locale))
+ language_layout = QtWidgets.QHBoxLayout()
+ language_layout.addWidget(language_label)
+ language_layout.addWidget(self.language_combobox)
+ language_layout.addStretch()
+
# Connection type: either automatic, control port, or socket file
# Bundled Tor
- self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True))
+ self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option'))
self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled)
# Bundled Tor doesn't work on dev mode in Windows or Mac
@@ -151,49 +267,34 @@ class SettingsDialog(QtWidgets.QDialog):
# Bridge options for bundled tor
# No bridges option radio
- self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option', True))
+ self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option'))
self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled)
# obfs4 option radio
# if the obfs4proxy binary is missing, we can't use obfs4 transports
- (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
+ (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
if not os.path.isfile(self.obfs4proxy_file_path):
- self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True))
+ self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy'))
self.tor_bridges_use_obfs4_radio.setEnabled(False)
else:
- self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True))
+ self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option'))
self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled)
- # meek_lite-amazon option radio
- # if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports
- (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
- if not os.path.isfile(self.obfs4proxy_file_path):
- self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True))
- self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False)
- else:
- self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option', True))
- self.tor_bridges_use_meek_lite_amazon_radio.toggled.connect(self.tor_bridges_use_meek_lite_amazon_radio_toggled)
-
# meek_lite-azure option radio
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
- (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
+ (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
if not os.path.isfile(self.obfs4proxy_file_path):
- self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True))
+ self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy'))
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
else:
- self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option', True))
+ self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option'))
self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled)
- # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser
- if self.system == 'Windows' or self.system == 'Darwin':
- self.tor_bridges_use_meek_lite_amazon_radio.hide()
- self.tor_bridges_use_meek_lite_azure_radio.hide()
-
# Custom bridges radio and textbox
- self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True))
+ self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option'))
self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled)
- self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label', True))
+ self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label'))
self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
self.tor_bridges_use_custom_label.setOpenExternalLinks(True)
self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit()
@@ -212,7 +313,6 @@ class SettingsDialog(QtWidgets.QDialog):
bridges_layout = QtWidgets.QVBoxLayout()
bridges_layout.addWidget(self.tor_bridges_no_bridges_radio)
bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio)
- bridges_layout.addWidget(self.tor_bridges_use_meek_lite_amazon_radio)
bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options)
@@ -221,14 +321,14 @@ class SettingsDialog(QtWidgets.QDialog):
self.bridges.setLayout(bridges_layout)
# Automatic
- self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True))
+ self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option'))
self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled)
# Control port
- self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True))
+ self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option'))
self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled)
- connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True))
+ connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label'))
self.connection_type_control_port_extras_address = QtWidgets.QLineEdit()
self.connection_type_control_port_extras_port = QtWidgets.QLineEdit()
connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout()
@@ -241,10 +341,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.connection_type_control_port_extras.hide()
# Socket file
- self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True))
+ self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option'))
self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled)
- connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True))
+ connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label'))
self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit()
connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout()
connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label)
@@ -255,7 +355,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.connection_type_socket_file_extras.hide()
# Tor SOCKS address and port
- gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label', True))
+ gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label'))
self.connection_type_socks_address = QtWidgets.QLineEdit()
self.connection_type_socks_port = QtWidgets.QLineEdit()
connection_type_socks_layout = QtWidgets.QHBoxLayout()
@@ -270,14 +370,14 @@ class SettingsDialog(QtWidgets.QDialog):
# Authentication options
# No authentication
- self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True))
+ self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option'))
self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled)
# Password
- self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True))
+ self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option'))
self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled)
- authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True))
+ authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label'))
self.authenticate_password_extras_password = QtWidgets.QLineEdit('')
authenticate_password_extras_layout = QtWidgets.QHBoxLayout()
authenticate_password_extras_layout.addWidget(authenticate_password_extras_label)
@@ -292,47 +392,49 @@ class SettingsDialog(QtWidgets.QDialog):
authenticate_group_layout.addWidget(self.authenticate_no_auth_radio)
authenticate_group_layout.addWidget(self.authenticate_password_radio)
authenticate_group_layout.addWidget(self.authenticate_password_extras)
- self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True))
+ self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label"))
self.authenticate_group.setLayout(authenticate_group_layout)
- # Test tor settings button
- self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True))
- self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
-
# Put the radios into their own group so they are exclusive
connection_type_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio)
connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio)
connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio)
connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio)
- connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True))
+ connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label"))
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
- # Connection type layout
- connection_type_group_layout = QtWidgets.QVBoxLayout()
- connection_type_group_layout.addWidget(self.connection_type_control_port_extras)
- connection_type_group_layout.addWidget(self.connection_type_socket_file_extras)
- connection_type_group_layout.addWidget(self.connection_type_socks)
- connection_type_group_layout.addWidget(self.authenticate_group)
- connection_type_group_layout.addWidget(self.connection_type_test_button)
- connection_type_group = QtWidgets.QGroupBox()
- connection_type_group.setLayout(connection_type_group_layout)
-
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
- self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True))
+ self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges"))
self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout)
self.connection_type_bridges_radio_group.hide()
+ # Test tor settings button
+ self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button'))
+ self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
+ connection_type_test_button_layout = QtWidgets.QHBoxLayout()
+ connection_type_test_button_layout.addWidget(self.connection_type_test_button)
+ connection_type_test_button_layout.addStretch()
+
+ # Connection type layout
+ connection_type_layout = QtWidgets.QVBoxLayout()
+ connection_type_layout.addWidget(self.connection_type_control_port_extras)
+ connection_type_layout.addWidget(self.connection_type_socket_file_extras)
+ connection_type_layout.addWidget(self.connection_type_socks)
+ connection_type_layout.addWidget(self.authenticate_group)
+ connection_type_layout.addWidget(self.connection_type_bridges_radio_group)
+ connection_type_layout.addLayout(connection_type_test_button_layout)
+
# Buttons
- self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True))
+ self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save'))
self.save_button.clicked.connect(self.save_clicked)
- self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
+ self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel'))
self.cancel_button.clicked.connect(self.cancel_clicked)
- version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version()))
- version_label.setStyleSheet('color: #666666')
- self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True))
+ version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version))
+ version_label.setStyleSheet(self.common.css['settings_version'])
+ self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help'))
self.help_button.clicked.connect(self.help_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(version_label)
@@ -343,20 +445,22 @@ class SettingsDialog(QtWidgets.QDialog):
# Tor network connection status
self.tor_status = QtWidgets.QLabel()
- self.tor_status.setStyleSheet('background-color: #ffffff; color: #000000; padding: 10px')
+ self.tor_status.setStyleSheet(self.common.css['settings_tor_status'])
self.tor_status.hide()
# Layout
left_col_layout = QtWidgets.QVBoxLayout()
+ left_col_layout.addWidget(general_group)
+ left_col_layout.addWidget(onion_group)
left_col_layout.addWidget(sharing_group)
- left_col_layout.addWidget(stealth_group)
+ left_col_layout.addWidget(receiving_group)
left_col_layout.addWidget(autoupdate_group)
+ left_col_layout.addLayout(language_layout)
left_col_layout.addStretch()
right_col_layout = QtWidgets.QVBoxLayout()
right_col_layout.addWidget(connection_type_radio_group)
- right_col_layout.addWidget(connection_type_group)
- right_col_layout.addWidget(self.connection_type_bridges_radio_group)
+ right_col_layout.addLayout(connection_type_layout)
right_col_layout.addWidget(self.tor_status)
right_col_layout.addStretch()
@@ -371,8 +475,11 @@ class SettingsDialog(QtWidgets.QDialog):
self.setLayout(layout)
self.cancel_button.setFocus()
+ self.reload_settings()
+
+ def reload_settings(self):
# Load settings, and fill them in
- self.old_settings = Settings(self.config)
+ self.old_settings = Settings(self.common, self.config)
self.old_settings.load()
close_after_first_download = self.old_settings.get('close_after_first_download')
@@ -381,12 +488,6 @@ class SettingsDialog(QtWidgets.QDialog):
else:
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
- systray_notifications = self.old_settings.get('systray_notifications')
- if systray_notifications:
- self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked)
- else:
- self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked)
-
shutdown_timeout = self.old_settings.get('shutdown_timeout')
if shutdown_timeout:
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -399,11 +500,30 @@ class SettingsDialog(QtWidgets.QDialog):
else:
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions')
+
+ if use_legacy_v2_onions:
+ self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
+ self.use_stealth_widget.show()
+ else:
+ self.use_stealth_widget.hide()
+
+ data_dir = self.old_settings.get('data_dir')
+ self.data_dir_lineedit.setText(data_dir)
+
+ public_mode = self.old_settings.get('public_mode')
+ if public_mode:
+ self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked)
+ else:
+ self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
+
use_stealth = self.old_settings.get('use_stealth')
if use_stealth:
self.stealth_checkbox.setCheckState(QtCore.Qt.Checked)
- if save_private_key:
- hidservauth_details.show()
+ # Legacy v2 mode is forced on if Stealth is enabled
+ self.use_legacy_v2_onions_checkbox.setEnabled(False)
+ if save_private_key and self.old_settings.get('hidservauth_string') != "":
+ self.hidservauth_details.show()
self.hidservauth_copy_button.show()
else:
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
@@ -417,6 +537,10 @@ class SettingsDialog(QtWidgets.QDialog):
autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp')
self._update_autoupdate_timestamp(autoupdate_timestamp)
+ locale = self.old_settings.get('locale')
+ locale_index = self.language_combobox.findData(QtCore.QVariant(locale))
+ self.language_combobox.setCurrentIndex(locale_index)
+
connection_type = self.old_settings.get('connection_type')
if connection_type == 'bundled':
if self.connection_type_bundled_radio.isEnabled():
@@ -445,13 +569,11 @@ class SettingsDialog(QtWidgets.QDialog):
if self.old_settings.get('no_bridges'):
self.tor_bridges_no_bridges_radio.setChecked(True)
self.tor_bridges_use_obfs4_radio.setChecked(False)
- self.tor_bridges_use_meek_lite_amazon_radio.setChecked(False)
self.tor_bridges_use_meek_lite_azure_radio.setChecked(False)
self.tor_bridges_use_custom_radio.setChecked(False)
else:
self.tor_bridges_no_bridges_radio.setChecked(False)
self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4'))
- self.tor_bridges_use_meek_lite_amazon_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_amazon'))
self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure'))
if self.old_settings.get('tor_bridges_use_custom_bridges'):
@@ -466,11 +588,29 @@ class SettingsDialog(QtWidgets.QDialog):
new_bridges = ''.join(new_bridges)
self.tor_bridges_use_custom_textbox.setPlainText(new_bridges)
+ # If we're connected to Tor, show onion service settings, show label if not
+ if self.onion.is_authenticated():
+ self.connect_to_tor_label.hide()
+ self.onion_settings_widget.show()
+
+ # If v3 onion services are supported, allow using legacy mode
+ if self.onion.supports_v3_onions:
+ self.common.log('SettingsDialog', '__init__', 'v3 onions are supported')
+ self.use_legacy_v2_onions_checkbox.show()
+ else:
+ self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported')
+ self.use_legacy_v2_onions_widget.hide()
+ self.use_legacy_v2_onions_checkbox_clicked(True)
+ else:
+ self.connect_to_tor_label.show()
+ self.onion_settings_widget.hide()
+
+
def connection_type_bundled_toggled(self, checked):
"""
Connection type bundled was toggled. If checked, hide authentication fields.
"""
- common.log('SettingsDialog', 'connection_type_bundled_toggled')
+ self.common.log('SettingsDialog', 'connection_type_bundled_toggled')
if checked:
self.authenticate_group.hide()
self.connection_type_socks.hide()
@@ -490,19 +630,15 @@ class SettingsDialog(QtWidgets.QDialog):
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
- def tor_bridges_use_meek_lite_amazon_radio_toggled(self, checked):
- """
- meek_lite-amazon bridges option was toggled. If checked, disable custom bridge options.
- """
- if checked:
- self.tor_bridges_use_custom_textbox_options.hide()
-
def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked):
"""
meek_lite_azure bridges option was toggled. If checked, disable custom bridge options.
"""
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
+ # Alert the user about meek's costliness if it looks like they're turning it on
+ if not self.old_settings.get('tor_bridges_use_meek_lite_azure'):
+ Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning)
def tor_bridges_use_custom_radio_toggled(self, checked):
"""
@@ -515,7 +651,7 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Connection type automatic was toggled. If checked, hide authentication fields.
"""
- common.log('SettingsDialog', 'connection_type_automatic_toggled')
+ self.common.log('SettingsDialog', 'connection_type_automatic_toggled')
if checked:
self.authenticate_group.hide()
self.connection_type_socks.hide()
@@ -526,7 +662,7 @@ class SettingsDialog(QtWidgets.QDialog):
Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields.
"""
- common.log('SettingsDialog', 'connection_type_control_port_toggled')
+ self.common.log('SettingsDialog', 'connection_type_control_port_toggled')
if checked:
self.authenticate_group.show()
self.connection_type_control_port_extras.show()
@@ -541,7 +677,7 @@ class SettingsDialog(QtWidgets.QDialog):
Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields.
"""
- common.log('SettingsDialog', 'connection_type_socket_file_toggled')
+ self.common.log('SettingsDialog', 'connection_type_socket_file_toggled')
if checked:
self.authenticate_group.show()
self.connection_type_socket_file_extras.show()
@@ -554,14 +690,14 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Authentication option no authentication was toggled.
"""
- common.log('SettingsDialog', 'authenticate_no_auth_toggled')
+ self.common.log('SettingsDialog', 'authenticate_no_auth_toggled')
def authenticate_password_toggled(self, checked):
"""
Authentication option password was toggled. If checked, show extra fields
for password auth. If unchecked, hide those extra fields.
"""
- common.log('SettingsDialog', 'authenticate_password_toggled')
+ self.common.log('SettingsDialog', 'authenticate_password_toggled')
if checked:
self.authenticate_password_extras.show()
else:
@@ -572,16 +708,47 @@ class SettingsDialog(QtWidgets.QDialog):
Toggle the 'Copy HidServAuth' button
to copy the saved HidServAuth to clipboard.
"""
- common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
+ self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
clipboard = self.qtapp.clipboard()
clipboard.setText(self.old_settings.get('hidservauth_string'))
+ def use_legacy_v2_onions_checkbox_clicked(self, checked):
+ """
+ Show the legacy settings if the legacy mode is enabled.
+ """
+ if checked:
+ self.use_stealth_widget.show()
+ else:
+ self.use_stealth_widget.hide()
+
+ def stealth_checkbox_clicked_connect(self, checked):
+ """
+ Prevent the v2 legacy mode being switched off if stealth is enabled
+ """
+ if checked:
+ self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
+ self.use_legacy_v2_onions_checkbox.setEnabled(False)
+ else:
+ self.use_legacy_v2_onions_checkbox.setEnabled(True)
+
+ def data_dir_button_clicked(self):
+ """
+ Browse for a new OnionShare data directory
+ """
+ data_dir = self.data_dir_lineedit.text()
+ selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self,
+ strings._('gui_settings_data_dir_label'), data_dir)
+
+ if selected_dir:
+ self.common.log('SettingsDialog', 'data_dir_button_clicked', 'selected dir: {}'.format(selected_dir))
+ self.data_dir_lineedit.setText(selected_dir)
+
def test_tor_clicked(self):
"""
Test Tor Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor.
"""
- common.log('SettingsDialog', 'test_tor_clicked')
+ self.common.log('SettingsDialog', 'test_tor_clicked')
settings = self.settings_from_fields()
try:
@@ -596,17 +763,17 @@ class SettingsDialog(QtWidgets.QDialog):
else:
tor_status_update_func = None
- onion = Onion()
- onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
+ onion = Onion(self.common)
+ onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
# If an exception hasn't been raised yet, the Tor settings work
- Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
+ Alert(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions))
# Clean up
onion.cleanup()
except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
- Alert(e.args[0], QtWidgets.QMessageBox.Warning)
+ Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning)
if settings.get('connection_type') == 'bundled':
self.tor_status.hide()
self._enable_buttons()
@@ -615,14 +782,14 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Check for Updates button clicked. Manually force an update check.
"""
- common.log('SettingsDialog', 'check_for_updates')
+ self.common.log('SettingsDialog', 'check_for_updates')
# Disable buttons
self._disable_buttons()
self.qtapp.processEvents()
def update_timestamp():
# Update the last checked label
- settings = Settings(self.config)
+ settings = Settings(self.common, self.config)
settings.load()
autoupdate_timestamp = settings.get('autoupdate_timestamp')
self._update_autoupdate_timestamp(autoupdate_timestamp)
@@ -636,22 +803,22 @@ class SettingsDialog(QtWidgets.QDialog):
# Check for updates
def update_available(update_url, installed_version, latest_version):
- Alert(strings._("update_available", True).format(update_url, installed_version, latest_version))
+ Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version))
close_forced_update_thread()
def update_not_available():
- Alert(strings._('update_not_available', True))
+ Alert(self.common, strings._('update_not_available'))
close_forced_update_thread()
def update_error():
- Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning)
+ Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning)
close_forced_update_thread()
- def update_invalid_version():
- Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning)
+ def update_invalid_version(latest_version):
+ Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning)
close_forced_update_thread()
- forced_update_thread = UpdateThread(self.onion, self.config, force=True)
+ forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True)
forced_update_thread.update_available.connect(update_available)
forced_update_thread.update_not_available.connect(update_not_available)
forced_update_thread.update_error.connect(update_error)
@@ -662,57 +829,73 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Save button clicked. Save current settings to disk.
"""
- common.log('SettingsDialog', 'save_clicked')
+ self.common.log('SettingsDialog', 'save_clicked')
+
+ def changed(s1, s2, keys):
+ """
+ Compare the Settings objects s1 and s2 and return true if any values
+ have changed for the given keys.
+ """
+ for key in keys:
+ if s1.get(key) != s2.get(key):
+ return True
+ return False
settings = self.settings_from_fields()
if settings:
+ # If language changed, inform user they need to restart OnionShare
+ if changed(settings, self.old_settings, ['locale']):
+ # Look up error message in different locale
+ new_locale = settings.get('locale')
+ if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]:
+ notice = strings.translations[new_locale]['gui_settings_language_changed_notice']
+ else:
+ notice = strings._('gui_settings_language_changed_notice')
+ Alert(self.common, notice, QtWidgets.QMessageBox.Information)
+
+ # Save the new settings
settings.save()
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
# the Onion object
reboot_onion = False
- if self.onion.is_authenticated():
- common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
- def changed(s1, s2, keys):
- """
- Compare the Settings objects s1 and s2 and return true if any values
- have changed for the given keys.
- """
- for key in keys:
- if s1.get(key) != s2.get(key):
- return True
- return False
-
- if changed(settings, self.old_settings, [
- 'connection_type', 'control_port_address',
- 'control_port_port', 'socks_address', 'socks_port',
- 'socket_file_path', 'auth_type', 'auth_password',
- 'no_bridges', 'tor_bridges_use_obfs4',
- 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure',
- 'tor_bridges_use_custom_bridges']):
-
+ if not self.local_only:
+ if self.onion.is_authenticated():
+ self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
+
+ if changed(settings, self.old_settings, [
+ 'connection_type', 'control_port_address',
+ 'control_port_port', 'socks_address', 'socks_port',
+ 'socket_file_path', 'auth_type', 'auth_password',
+ 'no_bridges', 'tor_bridges_use_obfs4',
+ 'tor_bridges_use_meek_lite_azure',
+ 'tor_bridges_use_custom_bridges']):
+
+ reboot_onion = True
+
+ else:
+ self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor')
+ # Tor isn't connected, so try connecting
reboot_onion = True
- else:
- common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor')
- # Tor isn't connected, so try connecting
- reboot_onion = True
+ # Do we need to reinitialize Tor?
+ if reboot_onion:
+ # Reinitialize the Onion object
+ self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion')
+ self.onion.cleanup()
- # Do we need to reinitialize Tor?
- if reboot_onion:
- # Reinitialize the Onion object
- common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion')
- self.onion.cleanup()
+ tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings)
+ tor_con.start()
- tor_con = TorConnectionDialog(self.qtapp, settings, self.onion)
- tor_con.start()
+ self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor))
- common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor))
+ if self.onion.is_authenticated() and not tor_con.wasCanceled():
+ self.settings_saved.emit()
+ self.close()
- if self.onion.is_authenticated() and not tor_con.wasCanceled():
+ else:
self.settings_saved.emit()
self.close()
-
else:
self.settings_saved.emit()
self.close()
@@ -721,9 +904,9 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Cancel button clicked.
"""
- common.log('SettingsDialog', 'cancel_clicked')
- if not self.onion.is_authenticated():
- Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning)
+ self.common.log('SettingsDialog', 'cancel_clicked')
+ if not self.local_only and not self.onion.is_authenticated():
+ Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning)
sys.exit()
else:
self.close()
@@ -732,21 +915,31 @@ class SettingsDialog(QtWidgets.QDialog):
"""
Help button clicked.
"""
- common.log('SettingsDialog', 'help_clicked')
- help_site = 'https://github.com/micahflee/onionshare/wiki'
- QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site))
+ self.common.log('SettingsDialog', 'help_clicked')
+ SettingsDialog.open_help()
+
+ @staticmethod
+ def open_help():
+ help_url = 'https://github.com/micahflee/onionshare/wiki'
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url))
def settings_from_fields(self):
"""
Return a Settings object that's full of values from the settings dialog.
"""
- common.log('SettingsDialog', 'settings_from_fields')
- settings = Settings(self.config)
+ self.common.log('SettingsDialog', 'settings_from_fields')
+ settings = Settings(self.common, self.config)
settings.load() # To get the last update timestamp
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
- settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked())
settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked())
+
+ # Complicated logic here to force v2 onion mode on or off depending on other settings
+ if self.use_legacy_v2_onions_checkbox.isChecked():
+ use_legacy_v2_onions = True
+ else:
+ use_legacy_v2_onions = False
+
if self.save_private_key_checkbox.isChecked():
settings.set('save_private_key', True)
settings.set('private_key', self.old_settings.get('private_key'))
@@ -758,11 +951,25 @@ class SettingsDialog(QtWidgets.QDialog):
settings.set('slug', '')
# Also unset the HidServAuth if we are removing our reusable private key
settings.set('hidservauth_string', '')
+
+ if use_legacy_v2_onions:
+ settings.set('use_legacy_v2_onions', True)
+ else:
+ settings.set('use_legacy_v2_onions', False)
+
+ settings.set('data_dir', self.data_dir_lineedit.text())
+ settings.set('public_mode', self.public_mode_checkbox.isChecked())
settings.set('use_stealth', self.stealth_checkbox.isChecked())
# Always unset the HidServAuth if Stealth mode is unset
if not self.stealth_checkbox.isChecked():
settings.set('hidservauth_string', '')
+ # Language
+ locale_index = self.language_combobox.currentIndex()
+ locale = self.language_combobox.itemData(locale_index)
+ settings.set('locale', locale)
+
+ # Tor connection
if self.connection_type_bundled_radio.isChecked():
settings.set('connection_type', 'bundled')
if self.connection_type_automatic_radio.isChecked():
@@ -795,31 +1002,21 @@ class SettingsDialog(QtWidgets.QDialog):
if self.tor_bridges_no_bridges_radio.isChecked():
settings.set('no_bridges', True)
settings.set('tor_bridges_use_obfs4', False)
- settings.set('tor_bridges_use_meek_lite_amazon', False)
settings.set('tor_bridges_use_meek_lite_azure', False)
settings.set('tor_bridges_use_custom_bridges', '')
if self.tor_bridges_use_obfs4_radio.isChecked():
settings.set('no_bridges', False)
settings.set('tor_bridges_use_obfs4', True)
- settings.set('tor_bridges_use_meek_lite_amazon', False)
- settings.set('tor_bridges_use_meek_lite_azure', False)
- settings.set('tor_bridges_use_custom_bridges', '')
- if self.tor_bridges_use_meek_lite_amazon_radio.isChecked():
- settings.set('no_bridges', False)
- settings.set('tor_bridges_use_obfs4', False)
- settings.set('tor_bridges_use_meek_lite_amazon', True)
settings.set('tor_bridges_use_meek_lite_azure', False)
settings.set('tor_bridges_use_custom_bridges', '')
if self.tor_bridges_use_meek_lite_azure_radio.isChecked():
settings.set('no_bridges', False)
settings.set('tor_bridges_use_obfs4', False)
- settings.set('tor_bridges_use_meek_lite_amazon', False)
settings.set('tor_bridges_use_meek_lite_azure', True)
settings.set('tor_bridges_use_custom_bridges', '')
if self.tor_bridges_use_custom_radio.isChecked():
settings.set('no_bridges', False)
settings.set('tor_bridges_use_obfs4', False)
- settings.set('tor_bridges_use_meek_lite_amazon', False)
settings.set('tor_bridges_use_meek_lite_azure', False)
# Insert a 'Bridge' line at the start of each bridge.
@@ -835,10 +1032,8 @@ class SettingsDialog(QtWidgets.QDialog):
ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$")
meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)")
if ipv4_pattern.match(bridge) or \
- ipv6_pattern.match(bridge):
- new_bridges.append(''.join(['Bridge ', bridge, '\n']))
- bridges_valid = True
- if self.system != 'Windows' and self.system != 'Darwin' and meek_lite_pattern.match(bridge):
+ ipv6_pattern.match(bridge) or \
+ meek_lite_pattern.match(bridge):
new_bridges.append(''.join(['Bridge ', bridge, '\n']))
bridges_valid = True
@@ -846,41 +1041,42 @@ class SettingsDialog(QtWidgets.QDialog):
new_bridges = ''.join(new_bridges)
settings.set('tor_bridges_use_custom_bridges', new_bridges)
else:
- Alert(strings._('gui_settings_tor_bridges_invalid', True))
+ Alert(self.common, strings._('gui_settings_tor_bridges_invalid'))
settings.set('no_bridges', True)
return False
return settings
def closeEvent(self, e):
- common.log('SettingsDialog', 'closeEvent')
+ self.common.log('SettingsDialog', 'closeEvent')
# On close, if Tor isn't connected, then quit OnionShare altogether
- if not self.onion.is_authenticated():
- common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor')
+ if not self.local_only:
+ if not self.onion.is_authenticated():
+ self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor')
- # Wait 1ms for the event loop to finish, then quit
- QtCore.QTimer.singleShot(1, self.qtapp.quit)
+ # Wait 1ms for the event loop to finish, then quit
+ QtCore.QTimer.singleShot(1, self.qtapp.quit)
def _update_autoupdate_timestamp(self, autoupdate_timestamp):
- common.log('SettingsDialog', '_update_autoupdate_timestamp')
+ self.common.log('SettingsDialog', '_update_autoupdate_timestamp')
if autoupdate_timestamp:
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
last_checked = dt.strftime('%B %d, %Y %H:%M')
else:
- last_checked = strings._('gui_settings_autoupdate_timestamp_never', True)
- self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked))
+ last_checked = strings._('gui_settings_autoupdate_timestamp_never')
+ self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked))
def _tor_status_update(self, progress, summary):
- self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor', True), progress, summary))
+ self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor'), progress, summary))
self.qtapp.processEvents()
if 'Done' in summary:
self.tor_status.hide()
self._enable_buttons()
def _disable_buttons(self):
- common.log('SettingsDialog', '_disable_buttons')
+ self.common.log('SettingsDialog', '_disable_buttons')
self.check_for_updates_button.setEnabled(False)
self.connection_type_test_button.setEnabled(False)
@@ -888,7 +1084,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.cancel_button.setEnabled(False)
def _enable_buttons(self):
- common.log('SettingsDialog', '_enable_buttons')
+ self.common.log('SettingsDialog', '_enable_buttons')
# We can't check for updates if we're still not connected to Tor
if not self.onion.connected_to_tor:
self.check_for_updates_button.setEnabled(False)
diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py
new file mode 100644
index 00000000..3b05bebf
--- /dev/null
+++ b/onionshare_gui/threads.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import time
+from PyQt5 import QtCore
+
+from onionshare.onion import *
+
+
+class OnionThread(QtCore.QThread):
+ """
+ Starts the onion service, and waits for it to finish
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(OnionThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('OnionThread', '__init__')
+
+ # allow this thread to be terminated
+ self.setTerminationEnabled()
+
+ def run(self):
+ self.mode.common.log('OnionThread', 'run')
+
+ self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
+
+ # start onionshare http service in new thread
+ self.mode.web_thread = WebThread(self.mode)
+ self.mode.web_thread.start()
+
+ # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
+ time.sleep(0.2)
+
+ try:
+ self.mode.app.start_onion_service()
+ self.success.emit()
+
+ except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
+ self.error.emit(e.args[0])
+ return
+
+
+class WebThread(QtCore.QThread):
+ """
+ Starts the web service
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(WebThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('WebThread', '__init__')
+
+ def run(self):
+ self.mode.common.log('WebThread', 'run')
+ self.mode.app.choose_port()
+ self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.common.settings.get('slug'))
diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py
index 12730c32..2bcbf1a6 100644
--- a/onionshare_gui/tor_connection_dialog.py
+++ b/onionshare_gui/tor_connection_dialog.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
-from onionshare import strings, common
+from onionshare import strings
from onionshare.onion import *
-from .alert import Alert
+from .widgets import Alert
class TorConnectionDialog(QtWidgets.QProgressDialog):
"""
@@ -30,21 +30,28 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
"""
open_settings = QtCore.pyqtSignal()
- def __init__(self, qtapp, settings, onion):
+ def __init__(self, common, qtapp, onion, custom_settings=False):
super(TorConnectionDialog, self).__init__(None)
- common.log('TorConnectionDialog', '__init__')
+
+ self.common = common
+
+ if custom_settings:
+ self.settings = custom_settings
+ else:
+ self.settings = self.common.settings
+
+ self.common.log('TorConnectionDialog', '__init__')
self.qtapp = qtapp
- self.settings = settings
self.onion = onion
self.setWindowTitle("OnionShare")
- self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
+ self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.setModal(True)
self.setFixedSize(400, 150)
# Label
- self.setLabelText(strings._('connecting_to_tor', True))
+ self.setLabelText(strings._('connecting_to_tor'))
# Progress bar ticks from 0 to 100
self.setRange(0, 100)
@@ -55,9 +62,9 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self._tor_status_update(0, '')
def start(self):
- common.log('TorConnectionDialog', 'start')
+ self.common.log('TorConnectionDialog', 'start')
- t = TorConnectionThread(self, self.settings, self.onion)
+ t = TorConnectionThread(self.common, self.settings, self, self.onion)
t.tor_status_update.connect(self._tor_status_update)
t.connected_to_tor.connect(self._connected_to_tor)
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
@@ -74,17 +81,17 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
def _tor_status_update(self, progress, summary):
self.setValue(int(progress))
- self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary))
+ self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary))
def _connected_to_tor(self):
- common.log('TorConnectionDialog', '_connected_to_tor')
+ self.common.log('TorConnectionDialog', '_connected_to_tor')
self.active = False
# Close the dialog after connecting
self.setValue(self.maximum())
def _canceled_connecting_to_tor(self):
- common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
+ self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
self.active = False
self.onion.cleanup()
@@ -92,12 +99,12 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
QtCore.QTimer.singleShot(1, self.cancel)
def _error_connecting_to_tor(self, msg):
- common.log('TorConnectionDialog', '_error_connecting_to_tor')
+ self.common.log('TorConnectionDialog', '_error_connecting_to_tor')
self.active = False
def alert_and_open_settings():
# Display the exception in an alert box
- Alert("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning)
+ Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning)
# Open settings
self.open_settings.emit()
@@ -113,16 +120,20 @@ class TorConnectionThread(QtCore.QThread):
canceled_connecting_to_tor = QtCore.pyqtSignal()
error_connecting_to_tor = QtCore.pyqtSignal(str)
- def __init__(self, dialog, settings, onion):
+ def __init__(self, common, settings, dialog, onion):
super(TorConnectionThread, self).__init__()
- common.log('TorConnectionThread', '__init__')
- self.dialog = dialog
+ self.common = common
+
+ self.common.log('TorConnectionThread', '__init__')
+
self.settings = settings
+
+ self.dialog = dialog
self.onion = onion
def run(self):
- common.log('TorConnectionThread', 'run')
+ self.common.log('TorConnectionThread', 'run')
# Connect to the Onion
try:
@@ -133,11 +144,11 @@ class TorConnectionThread(QtCore.QThread):
self.canceled_connecting_to_tor.emit()
except BundledTorCanceled as e:
- common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
+ self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
self.canceled_connecting_to_tor.emit()
except Exception as e:
- common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
+ self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
self.error_connecting_to_tor.emit(str(e.args[0]))
def _tor_status_update(self, progress, summary):
diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py
index 5d73efb4..a7e0b99c 100644
--- a/onionshare_gui/update_checker.py
+++ b/onionshare_gui/update_checker.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,13 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore
import datetime, time, socket, re, platform
+import socks
from distutils.version import LooseVersion as Version
-from onionshare import socks
from onionshare.settings import Settings
from onionshare.onion import Onion
-from . import strings, common
+from . import strings
class UpdateCheckerCheckError(Exception):
"""
@@ -53,18 +53,21 @@ class UpdateChecker(QtCore.QObject):
update_available = QtCore.pyqtSignal(str, str, str)
update_not_available = QtCore.pyqtSignal()
update_error = QtCore.pyqtSignal()
- update_invalid_version = QtCore.pyqtSignal()
+ update_invalid_version = QtCore.pyqtSignal(str)
- def __init__(self, onion, config=False):
+ def __init__(self, common, onion, config=False):
super(UpdateChecker, self).__init__()
- common.log('UpdateChecker', '__init__')
+
+ self.common = common
+
+ self.common.log('UpdateChecker', '__init__')
self.onion = onion
self.config = config
def check(self, force=False, config=False):
- common.log('UpdateChecker', 'check', 'force={}'.format(force))
+ self.common.log('UpdateChecker', 'check', 'force={}'.format(force))
# Load the settings
- settings = Settings(config)
+ settings = Settings(self.common, config)
settings.load()
# If force=True, then definitely check
@@ -87,11 +90,11 @@ class UpdateChecker(QtCore.QObject):
# Check for updates
if check_for_updates:
- common.log('UpdateChecker', 'check', 'checking for updates')
+ self.common.log('UpdateChecker', 'check', 'checking for updates')
# Download the latest-version file over Tor
try:
# User agent string includes OnionShare version and platform
- user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system())
+ user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform)
# If the update is forced, add '?force=1' to the URL, to more
# accurately measure daily users
@@ -104,7 +107,7 @@ class UpdateChecker(QtCore.QObject):
else:
onion_domain = 'elx57ue5uyfplgva.onion'
- common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
+ self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
(socks_address, socks_port) = self.onion.get_tor_socks_port()
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
@@ -122,10 +125,10 @@ class UpdateChecker(QtCore.QObject):
http_response = s.recv(1024)
latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
- common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
+ self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
except Exception as e:
- common.log('UpdateChecker', 'check', '{}'.format(e))
+ self.common.log('UpdateChecker', 'check', '{}'.format(e))
self.update_error.emit()
raise UpdateCheckerCheckError
@@ -133,7 +136,7 @@ class UpdateChecker(QtCore.QObject):
# This regex is: 1-3 dot-separated numeric components
version_re = r"^(\d+\.)?(\d+\.)?(\d+)$"
if not re.match(version_re, latest_version):
- self.update_invalid_version.emit()
+ self.update_invalid_version.emit(latest_version)
raise UpdateCheckerInvalidLatestVersion(latest_version)
# Update the last checked timestamp (dropping the seconds and milliseconds)
@@ -145,7 +148,7 @@ class UpdateChecker(QtCore.QObject):
# Do we need to update?
update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
- installed_version = common.get_version()
+ installed_version = self.common.version
if installed_version < latest_version:
self.update_available.emit(update_url, installed_version, latest_version)
return
@@ -157,19 +160,22 @@ class UpdateThread(QtCore.QThread):
update_available = QtCore.pyqtSignal(str, str, str)
update_not_available = QtCore.pyqtSignal()
update_error = QtCore.pyqtSignal()
- update_invalid_version = QtCore.pyqtSignal()
+ update_invalid_version = QtCore.pyqtSignal(str)
- def __init__(self, onion, config=False, force=False):
+ def __init__(self, common, onion, config=False, force=False):
super(UpdateThread, self).__init__()
- common.log('UpdateThread', '__init__')
+
+ self.common = common
+
+ self.common.log('UpdateThread', '__init__')
self.onion = onion
self.config = config
self.force = force
def run(self):
- common.log('UpdateThread', 'run')
+ self.common.log('UpdateThread', 'run')
- u = UpdateChecker(self.onion, self.config)
+ u = UpdateChecker(self.common, self.onion, self.config)
u.update_available.connect(self._update_available)
u.update_not_available.connect(self._update_not_available)
u.update_error.connect(self._update_error)
@@ -179,25 +185,25 @@ class UpdateThread(QtCore.QThread):
u.check(config=self.config,force=self.force)
except Exception as e:
# If update check fails, silently ignore
- common.log('UpdateThread', 'run', '{}'.format(e))
+ self.common.log('UpdateThread', 'run', '{}'.format(e))
pass
def _update_available(self, update_url, installed_version, latest_version):
- common.log('UpdateThread', '_update_available')
+ self.common.log('UpdateThread', '_update_available')
self.active = False
self.update_available.emit(update_url, installed_version, latest_version)
def _update_not_available(self):
- common.log('UpdateThread', '_update_not_available')
+ self.common.log('UpdateThread', '_update_not_available')
self.active = False
self.update_not_available.emit()
def _update_error(self):
- common.log('UpdateThread', '_update_error')
+ self.common.log('UpdateThread', '_update_error')
self.active = False
self.update_error.emit()
- def _update_invalid_version(self):
- common.log('UpdateThread', '_update_invalid_version')
+ def _update_invalid_version(self, latest_version):
+ self.common.log('UpdateThread', '_update_invalid_version')
self.active = False
- self.update_invalid_version.emit()
+ self.update_invalid_version.emit(latest_version)
diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py
new file mode 100644
index 00000000..600165aa
--- /dev/null
+++ b/onionshare_gui/widgets.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+class Alert(QtWidgets.QMessageBox):
+ """
+ An alert box dialog.
+ """
+ def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
+ super(Alert, self).__init__(None)
+
+ self.common = common
+
+ self.common.log('Alert', '__init__')
+
+ self.setWindowTitle("OnionShare")
+ self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
+ self.setText(message)
+ self.setIcon(icon)
+ self.setStandardButtons(buttons)
+
+ if autostart:
+ self.exec_()
+
+
+class AddFileDialog(QtWidgets.QFileDialog):
+ """
+ Overridden version of QFileDialog which allows us to select folders as well
+ as, or instead of, files. For adding files/folders to share.
+
+ Note that this dialog can't be used in macOS, only in Windows, Linux, and BSD.
+ This is because the macOS sandbox requires native dialogs, and this is a Qt5
+ dialog.
+ """
+ def __init__(self, common, *args, **kwargs):
+ QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
+
+ self.common = common
+ self.common.log('AddFileDialog', '__init__')
+
+ self.setOption(self.DontUseNativeDialog, True)
+ self.setOption(self.ReadOnly, True)
+ self.setOption(self.ShowDirsOnly, False)
+ self.setFileMode(self.ExistingFiles)
+ tree_view = self.findChild(QtWidgets.QTreeView)
+ tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+ list_view = self.findChild(QtWidgets.QListView, "listView")
+ list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+
+ def accept(self):
+ self.common.log('AddFileDialog', 'accept')
+ QtWidgets.QDialog.accept(self)
diff --git a/screenshots/appdata-client.png b/screenshots/appdata-client.png
deleted file mode 100644
index 8fe99b11..00000000
--- a/screenshots/appdata-client.png
+++ /dev/null
Binary files differ
diff --git a/screenshots/appdata-onionshare-receive-client.png b/screenshots/appdata-onionshare-receive-client.png
new file mode 100644
index 00000000..8edcc326
--- /dev/null
+++ b/screenshots/appdata-onionshare-receive-client.png
Binary files differ
diff --git a/screenshots/appdata-onionshare-receive-server.png b/screenshots/appdata-onionshare-receive-server.png
new file mode 100644
index 00000000..121eab48
--- /dev/null
+++ b/screenshots/appdata-onionshare-receive-server.png
Binary files differ
diff --git a/screenshots/appdata-onionshare-share-client.png b/screenshots/appdata-onionshare-share-client.png
new file mode 100644
index 00000000..c57fb2a8
--- /dev/null
+++ b/screenshots/appdata-onionshare-share-client.png
Binary files differ
diff --git a/screenshots/appdata-onionshare-share-server.png b/screenshots/appdata-onionshare-share-server.png
new file mode 100644
index 00000000..d85e45ce
--- /dev/null
+++ b/screenshots/appdata-onionshare-share-server.png
Binary files differ
diff --git a/screenshots/appdata-server.png b/screenshots/appdata-server.png
deleted file mode 100644
index abdb2198..00000000
--- a/screenshots/appdata-server.png
+++ /dev/null
Binary files differ
diff --git a/screenshots/client.png b/screenshots/client.png
deleted file mode 100644
index fdf69fd0..00000000
--- a/screenshots/client.png
+++ /dev/null
Binary files differ
diff --git a/screenshots/onionshare-receive-client.png b/screenshots/onionshare-receive-client.png
new file mode 100644
index 00000000..be3f3585
--- /dev/null
+++ b/screenshots/onionshare-receive-client.png
Binary files differ
diff --git a/screenshots/onionshare-receive-server.png b/screenshots/onionshare-receive-server.png
new file mode 100644
index 00000000..283f4c41
--- /dev/null
+++ b/screenshots/onionshare-receive-server.png
Binary files differ
diff --git a/screenshots/onionshare-share-client.png b/screenshots/onionshare-share-client.png
new file mode 100644
index 00000000..58f102d4
--- /dev/null
+++ b/screenshots/onionshare-share-client.png
Binary files differ
diff --git a/screenshots/onionshare-share-server.png b/screenshots/onionshare-share-server.png
new file mode 100644
index 00000000..329fc8af
--- /dev/null
+++ b/screenshots/onionshare-share-server.png
Binary files differ
diff --git a/screenshots/server.png b/screenshots/server.png
deleted file mode 100644
index 8bdf2971..00000000
--- a/screenshots/server.png
+++ /dev/null
Binary files differ
diff --git a/setup.py b/setup.py
index 14cf05b1..f482abb6 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -31,28 +31,48 @@ def file_list(path):
version = open('share/version.txt').read().strip()
description = (
- """OnionShare lets you securely and anonymously share a file of any size with someone. """
- """It works by starting a web server, making it accessible as a Tor hidden service, """
- """and generating an unguessable URL to access and download the file.""")
-long_description = description + " " + (
- """It doesn't require setting up a server on the internet somewhere or using a third """
- """party filesharing service. You host the file on your own computer and use a Tor """
- """hidden service to make it temporarily accessible over the internet. The other user """
- """just needs to use Tor Browser to download the file from you."""
+ """OnionShare lets you securely and anonymously send and receive files. It """
+ """works by starting a web server, making it accessible as a Tor onion """
+ """service, and generating an unguessable web address so others can download """
+ """files from you, or upload files to you. It does _not_ require setting up """
+ """a separate server or using a third party file-sharing service."""
+)
+long_description = description + "\n\n" + (
+ """If you want to send files to someone, OnionShare hosts them on your own """
+ """computer and uses a Tor onion service to make them temporarily accessible """
+ """over the internet. The receiving user just needs to open the web address """
+ """in Tor Browser to download the files. If you want to receive files, """
+ """OnionShare hosts an anonymous dropbox directly on your computer and uses """
+ """a Tor onion service to make it temporarily accessible over the internet. """
+ """Other users can upload files to you from by loading the web address in """
+ """Tor Browser."""
)
author = 'Micah Lee'
author_email = 'micah@micahflee.com'
url = 'https://github.com/micahflee/onionshare'
license = 'GPL v3'
keywords = 'onion, share, onionshare, tor, anonymous, web server'
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "Framework :: Flask",
+ "Topic :: Communications :: File Sharing",
+ "Topic :: Security :: Cryptography",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+ "Intended Audience :: End Users/Desktop",
+ "Operating System :: OS Independent",
+ "Environment :: Web Environment"
+ ]
data_files=[
(os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']),
- (os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']),
+ (os.path.join(sys.prefix, 'share/metainfo'), ['install/onionshare.appdata.xml']),
(os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']),
(os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
(os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
(os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
- (os.path.join(sys.prefix, 'share/onionshare/html'), file_list('share/html')),
+ (os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')),
+ (os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')),
+ (os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')),
+ (os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js'))
]
if platform.system() != 'OpenBSD':
data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py']))
@@ -60,9 +80,16 @@ if platform.system() != 'OpenBSD':
setup(
name='onionshare', version=version,
description=description, long_description=long_description,
- author=author, author_email=author_email,
- url=url, license=license, keywords=keywords,
- packages=['onionshare', 'onionshare_gui'],
+ author=author, author_email=author_email, maintainer=author, maintainer_email=author_email,
+ url=url, license=license, keywords=keywords, classifiers=classifiers,
+ packages=[
+ 'onionshare',
+ 'onionshare.web',
+ 'onionshare_gui',
+ 'onionshare_gui.mode',
+ 'onionshare_gui.mode.share_mode',
+ 'onionshare_gui.mode.receive_mode'
+ ],
include_package_data=True,
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
data_files=data_files
diff --git a/share/html/404.html b/share/html/404.html
deleted file mode 100644
index 09d0fc3c..00000000
--- a/share/html/404.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>Error 404</title>
- <link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
- <style type="text/css">
- body {
- background-color: #FFC4D5;
- color: #FF0048;
- text-align: center;
- font-size: 20em;
- }
- </style>
- </head>
- <body>404</body>
-</html>
diff --git a/share/html/denied.html b/share/html/denied.html
deleted file mode 100644
index a82ac027..00000000
--- a/share/html/denied.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>OnionShare</title>
- <link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
- <style>
- body {
- background-color: #222222;
- color: #ffffff;
- text-align: center;
- font-family: sans-serif;
- padding: 5em 1em;
- }
- </style>
- </head>
- <body>
- <p>OnionShare download in progress</p>
- </body>
-</html>
diff --git a/share/html/index.html b/share/html/index.html
deleted file mode 100644
index 57711e02..00000000
--- a/share/html/index.html
+++ /dev/null
@@ -1,208 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>OnionShare</title>
- <link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
- <style type="text/css">
- .clearfix:after {
- content: ".";
- display: block;
- clear: both;
- visibility: hidden;
- line-height: 0;
- height: 0;
- }
-
- body {
- margin: 0;
- font-family: Helvetica;
- }
-
- header {
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
- background: #fcfcfc;
- background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%);
- padding: 0.8rem;
- }
-
- header .logo {
- vertical-align: middle;
- width: 45px;
- height: 45px;
- }
-
- header h1 {
- display: inline-block;
- margin: 0 0 0 0.5rem;
- vertical-align: middle;
- font-weight: normal;
- font-size: 1.5rem;
- color: #666666;
- }
-
- header .right {
- float: right;
- font-size: .75rem;
- }
-
- header .right ul li {
- display: inline;
- margin: 0 0 0 .5rem;
- font-size: 1rem;
- }
-
- header .button {
- color: #ffffff;
- background-color: #4e064f;
- padding: 10px;
- border-radius: 5px;
- text-decoration: none;
- margin-left: 1rem;
- cursor: pointer;
- }
-
- table.file-list {
- width: 100%;
- margin: 0 auto;
- border-collapse: collapse;
- }
-
- table.file-list th {
- text-align: left;
- text-transform: uppercase;
- font-weight: normal;
- color: #666666;
- padding: 0.5rem;
- }
-
- table.file-list tr {
- border-bottom: 1px solid #e0e0e0;
- }
-
- table.file-list td {
- white-space: nowrap;
- padding: 0.5rem 10rem 0.5rem 0.8rem;
- }
-
- table.file-list td img {
- vertical-align: middle;
- margin-right: 0.5rem;
- }
-
- table.file-list td:last-child {
- width: 100%;
- }
- </style>
- <meta name="onionshare-filename" content="{{ filename }}">
- <meta name="onionshare-filesize" content="{{ filesize }}">
- </head>
- <body>
-
- <header class="clearfix">
- <div class="right">
- <ul>
- <li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
- <li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
- </ul>
- </div>
- <img class="logo" src="data:image/png;base64,{{logo_b64}}" title="OnionShare">
- <h1>OnionShare</h1>
- </header>
-
- <table class="file-list" id="file-list">
- <tr>
- <th onclick="sortTable(0)">Filename</th>
- <th onclick="sortTable(1)">Size</th>
- <th></th>
- </tr>
- {% for info in file_info.dirs %}
- <tr>
- <td>
- <img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ folder_b64 }}" />
- {{ info.basename }}
- </td>
- <td>{{ info.size_human }}</td>
- <td></td>
- </tr>
- {% endfor %}
- {% for info in file_info.files %}
- <tr>
- <td>
- <img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ file_b64 }}" />
- {{ info.basename }}
- </td>
- <td>{{ info.size_human }}</td>
- <td></td>
- </tr>
- {% endfor %}
- </table>
- <script>
- // Function to convert human-readable sizes back to bytes, for sorting
- function unhumanize(text) {
- var powers = {'b': 0, 'k': 1, 'm': 2, 'g': 3, 't': 4};
- var regex = /(\d+(?:\.\d+)?)\s?(B|K|M|G|T)?/i;
- var res = regex.exec(text);
- if(res[2] === undefined) {
- // Account for alphabetical words (file/dir names)
- return text;
- } else {
- return res[1] * Math.pow(1024, powers[res[2].toLowerCase()]);
- }
- }
- function sortTable(n) {
- var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
- table = document.getElementById("file-list");
- switching = true;
- // Set the sorting direction to ascending:
- dir = "asc";
- /* Make a loop that will continue until
- no switching has been done: */
- while (switching) {
- // Start by saying: no switching is done:
- switching = false;
- rows = table.getElementsByTagName("TR");
- /* Loop through all table rows (except the
- first, which contains table headers): */
- for (i = 1; i < (rows.length - 1); i++) {
- // Start by saying there should be no switching:
- shouldSwitch = false;
- /* Get the two elements you want to compare,
- one from current row and one from the next: */
- x = rows[i].getElementsByTagName("TD")[n];
- y = rows[i + 1].getElementsByTagName("TD")[n];
- /* Check if the two rows should switch place,
- based on the direction, asc or desc: */
- if (dir == "asc") {
- if (unhumanize(x.innerHTML.toLowerCase()) > unhumanize(y.innerHTML.toLowerCase())) {
- // If so, mark as a switch and break the loop:
- shouldSwitch= true;
- break;
- }
- } else if (dir == "desc") {
- if (unhumanize(x.innerHTML.toLowerCase()) < unhumanize(y.innerHTML.toLowerCase())) {
- // If so, mark as a switch and break the loop:
- shouldSwitch= true;
- break;
- }
- }
- }
- if (shouldSwitch) {
- /* If a switch has been marked, make the switch
- and mark that a switch has been done: */
- rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
- switching = true;
- // Each time a switch is done, increase this count by 1:
- switchcount ++;
- } else {
- /* If no switching has been done AND the direction is "asc",
- set the direction to "desc" and run the while loop again. */
- if (switchcount == 0 && dir == "asc") {
- dir = "desc";
- switching = true;
- }
- }
- }
- }
- </script>
- </body>
-</html>
diff --git a/share/images/open_folder.png b/share/images/open_folder.png
new file mode 100644
index 00000000..0a734c41
--- /dev/null
+++ b/share/images/open_folder.png
Binary files differ
diff --git a/share/images/receive_icon_toggle.png b/share/images/receive_icon_toggle.png
new file mode 100644
index 00000000..846ececb
--- /dev/null
+++ b/share/images/receive_icon_toggle.png
Binary files differ
diff --git a/share/images/receive_icon_toggle_selected.png b/share/images/receive_icon_toggle_selected.png
new file mode 100644
index 00000000..127ce208
--- /dev/null
+++ b/share/images/receive_icon_toggle_selected.png
Binary files differ
diff --git a/share/images/receive_icon_transparent.png b/share/images/receive_icon_transparent.png
new file mode 100644
index 00000000..99207097
--- /dev/null
+++ b/share/images/receive_icon_transparent.png
Binary files differ
diff --git a/share/images/settings.png b/share/images/settings.png
index 4c69de07..ec35400a 100644
--- a/share/images/settings.png
+++ b/share/images/settings.png
Binary files differ
diff --git a/share/images/download_completed.png b/share/images/share_completed.png
index e68fe5a2..e68fe5a2 100644
--- a/share/images/download_completed.png
+++ b/share/images/share_completed.png
Binary files differ
diff --git a/share/images/download_completed_none.png b/share/images/share_completed_none.png
index 8dbd6939..8dbd6939 100644
--- a/share/images/download_completed_none.png
+++ b/share/images/share_completed_none.png
Binary files differ
diff --git a/share/images/share_icon_toggle.png b/share/images/share_icon_toggle.png
new file mode 100644
index 00000000..87303c9f
--- /dev/null
+++ b/share/images/share_icon_toggle.png
Binary files differ
diff --git a/share/images/share_icon_toggle_selected.png b/share/images/share_icon_toggle_selected.png
new file mode 100644
index 00000000..0ba52cff
--- /dev/null
+++ b/share/images/share_icon_toggle_selected.png
Binary files differ
diff --git a/share/images/share_icon_transparent.png b/share/images/share_icon_transparent.png
new file mode 100644
index 00000000..3648c3fb
--- /dev/null
+++ b/share/images/share_icon_transparent.png
Binary files differ
diff --git a/share/images/download_in_progress.png b/share/images/share_in_progress.png
index 19694659..19694659 100644
--- a/share/images/download_in_progress.png
+++ b/share/images/share_in_progress.png
Binary files differ
diff --git a/share/images/download_in_progress_none.png b/share/images/share_in_progress_none.png
index 2d61dba4..2d61dba4 100644
--- a/share/images/download_in_progress_none.png
+++ b/share/images/share_in_progress_none.png
Binary files differ
diff --git a/share/locale/am.json b/share/locale/am.json
new file mode 100644
index 00000000..d226d86e
--- /dev/null
+++ b/share/locale/am.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "ተወው",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "ተወው",
+ "gui_settings_button_help": "መመሪያ",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "አዎ",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/ar.json b/share/locale/ar.json
new file mode 100644
index 00000000..b70127fa
--- /dev/null
+++ b/share/locale/ar.json
@@ -0,0 +1,186 @@
+{
+ "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.",
+ "preparing_files": "جاري ضغط الملفات.",
+ "give_this_url": "أعط هذا العنوان للمتلقي:",
+ "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:",
+ "give_this_url_receive": "اعط هذا العنوان للمرسل:",
+ "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:",
+ "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف الخادم",
+ "not_a_file": "{0:s} ليس ملفا صالحا.",
+ "not_a_readable_file": "{0:s} ملف غير قابل للقراءة.",
+ "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)",
+ "other_page_loaded": "تم تحميل العنوان",
+ "close_on_timeout": "",
+ "closing_automatically": "توقف بسبب انتهاء التحميل",
+ "timeout_download_still_running": "انتظار اكتمال التحميل",
+ "large_filesize": "تحذير: ارسال مشاركة كبيرة قد يستغرق ساعات",
+ "systray_menu_exit": "خروج",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "اكمل المستخدم تحميل الملفات",
+ "systray_download_canceled_title": "تم الغاء التحميل",
+ "systray_download_canceled_message": "الغى المستخدم التحميل",
+ "systray_upload_started_title": "بدأ الرفع",
+ "systray_upload_started_message": "بدأ مستخدم رفع ملفات الى حاسوبك",
+ "help_local_only": "",
+ "help_stay_open": "استمر في المشاركة بعد اول تحميل",
+ "help_shutdown_timeout": "أوقف المشاركة بعد ثواني محددة",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "إضافة",
+ "gui_delete": "حذف",
+ "gui_choose_items": "إختر",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "ألغى",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "خروج",
+ "gui_quit_warning_dont_quit": "إلغاء",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "الإعدادات",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "التحقق من الإصدار الجديد",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "أبدا",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "الإعدادات العامة",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "كلمة السر",
+ "gui_settings_password_label": "كلمة السر",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "حفظ",
+ "gui_settings_button_cancel": "إلغاء",
+ "gui_settings_button_help": "مساعدة",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "نعم,",
+ "gui_tor_connection_ask_quit": "خروج",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "جاري الإستلام",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "استعراض",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": "",
+ "timeout_upload_still_running": "انتظار اكتمال الرفع"
+}
diff --git a/share/locale/bg.json b/share/locale/bg.json
new file mode 100644
index 00000000..256f27e0
--- /dev/null
+++ b/share/locale/bg.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "Създаване на onion услуга на порт {0:d}.",
+ "preparing_files": "Архивира документи.",
+ "give_this_url": "Дайте този адрес на получателя:",
+ "give_this_url_stealth": "Дайте този адрес и HidServAuth реда на получателя:",
+ "give_this_url_receive": "Дайте този адрес на подателя:",
+ "give_this_url_receive_stealth": "Дайте този адрес и HidServAuth на подателя:",
+ "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра",
+ "not_a_file": "{0: s) не е валиден документ.",
+ "not_a_readable_file": "{0:s) не е четаем файл.",
+ "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана",
+ "other_page_loaded": "Адресът е зареден",
+ "close_on_timeout": "Спряно, защото автоматично спиращият таймер приключи",
+ "closing_automatically": "Спряно, защото свалянето приключи",
+ "timeout_download_still_running": "Изчакване на свалянето да приключи",
+ "timeout_upload_still_running": "Изчакване ъплоудът да приключи",
+ "large_filesize": "Предупреждение: изпращане на голям дял може да отнеме часове",
+ "systray_menu_exit": "Изход",
+ "systray_download_started_title": "OnionShare сваляне започна",
+ "systray_download_started_message": "Потребител започна да сваля файловете Ви",
+ "systray_download_completed_title": "OnionShare свалянето приключи",
+ "systray_download_completed_message": "Потребителят приключи с изтегляне на Вашите файлове",
+ "systray_download_canceled_title": "OnionShare сваляне е отменено",
+ "systray_download_canceled_message": "Потребителят отмени свалянето",
+ "systray_upload_started_title": "OnionShare ъплоуд започна",
+ "systray_upload_started_message": "Ползвател започна да ъплоудва файлове на компютъра Ви",
+ "help_local_only": "Не използвайте Тор (само за разработване)",
+ "help_stay_open": "Продължи споделянето след първото изтегляне",
+ "help_shutdown_timeout": "Спри споделянето след дадено количество секунди",
+ "help_stealth": "Използвай клиент авторизация (напреднал)",
+ "help_receive": "Получаване на дялове вместо изпращане",
+ "help_debug": "Протоколирай OnionShare грешки на stdout и уеб грешки на диск",
+ "help_filename": "Списък на документи или папки за споделяне",
+ "help_config": "Персонализирано местоположение на JSON конфигурационен файл (незадължително)",
+ "gui_drag_and_drop": "Плъзнете и пуснете файлове и папки, \nза да започнете споделяне",
+ "gui_add": "Добавете",
+ "gui_delete": "Изтриване",
+ "gui_choose_items": "Изберете",
+ "gui_share_start_server": "Започнете споделянето",
+ "gui_share_stop_server": "Спрете споделянето",
+ "gui_share_stop_server_shutdown_timeout": "Спрете споделянето ({} остават)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Автоматично спиращият таймерът терминира в {}",
+ "gui_receive_start_server": "Стартирайте получаващ режим",
+ "gui_receive_stop_server": "Спрете получаващия режим",
+ "gui_receive_stop_server_shutdown_timeout": "Спрете получаващия режим ({} остават)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Автоматично спиращият таймер спира в {}",
+ "gui_copy_url": "Копирайте адрес",
+ "gui_copy_hidservauth": "Копирайте HidServAuth",
+ "gui_downloads": "Свалете история",
+ "gui_no_downloads": "Още няма изтегляния",
+ "gui_canceled": "Отменен",
+ "gui_copied_url_title": "OnionShare адресът е копиран",
+ "gui_copied_url": "OnionShare адресът е копиран към клипборда",
+ "gui_copied_hidservauth_title": "HidServAuth е копиран",
+ "gui_copied_hidservauth": "HidServAuth редът е копиран към клипборда",
+ "gui_please_wait": "Започва... кликни за отменяне.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} изтече.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (изчисляване)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Не толкова бързо",
+ "gui_share_quit_warning": "Намирате се в процес на изпращане на данни. Сигурни ли сте, че искате да спрете OnionShare?",
+ "gui_receive_quit_warning": "Намирате се в процес на получаване на файлове. Сигурни ли сте, че искате да спрете OnionShare?",
+ "gui_quit_warning_quit": "Изход",
+ "gui_quit_warning_dont_quit": "Отказ",
+ "error_rate_limit": "Някой е направил прекалено много грешни опити за адреса Ви, което означава, че може да се опитват да го отгатнат, така че OnionShare спря сървъра. Стартирайте споделянето отново и изпратете нов адрес на получателя за споделяне.",
+ "zip_progress_bar_format": "Компресира: %p%",
+ "error_stealth_not_supported": "За да използвате ауторизация на клиента Ви трябва поне Tor 0.2.9.1-alpha (или на браузъра 6.5) и python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare изисква поне Tor 0.2.7.1 и python3-stem 1.4.0.",
+ "gui_settings_window_title": "Настройки",
+ "gui_settings_whats_this": "<a href='{0:s}'>Какво е това?</a>",
+ "gui_settings_stealth_option": "Използвайте клиент ауторизация (наследствен)",
+ "gui_settings_stealth_hidservauth_string": "След като Вашия частен ключ бе запазен за повторна употреба, можете сега да кликнете, за да копирате Вашия HidServAuth.",
+ "gui_settings_autoupdate_label": "Провери за нова версия",
+ "gui_settings_autoupdate_option": "Уведоми ме, когато е налице нова версия",
+ "gui_settings_autoupdate_timestamp": "Последна проверка: {}",
+ "gui_settings_autoupdate_timestamp_never": "Никога",
+ "gui_settings_autoupdate_check_button": "Проверете за нова версия",
+ "gui_settings_general_label": "Общи настройки",
+ "gui_settings_sharing_label": "Настройки на споделяне",
+ "gui_settings_close_after_first_download_option": "Спри споделянето след първото изтегляне",
+ "gui_settings_connection_type_label": "Как OnionShare да се свържe с Тор?",
+ "gui_settings_connection_type_bundled_option": "Използвай Тор версия, вградена в OnionShare",
+ "gui_settings_connection_type_automatic_option": "Опит за автоматична конфигурация с Тор браузъра",
+ "gui_settings_connection_type_control_port_option": "Свържете, използвайки контролен порт",
+ "gui_settings_connection_type_socket_file_option": "Свържете се използвайки сокет",
+ "gui_settings_connection_type_test_button": "Тест на връзката с Тор",
+ "gui_settings_control_port_label": "Контролен порт",
+ "gui_settings_socket_file_label": "Сокет файл",
+ "gui_settings_socks_label": "SOCKS порт",
+ "gui_settings_authenticate_label": "Настройки на Тор за удостоверяване на автентичността",
+ "gui_settings_authenticate_no_auth_option": "Без автентикация или cookie автентикация",
+ "gui_settings_authenticate_password_option": "Парола",
+ "gui_settings_password_label": "Парола",
+ "gui_settings_tor_bridges": "Поддръжка на Тор мост",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Не използвайте мостове",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Използвайте вградените obfs4 pluggable транспорти",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Използвайте вградените obfs4 pluggable транспорти (изисква obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Използвайте вградените meek_lite (Azure) pluggable транспорти",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Използвайте вградените meek_lite (Azure) pluggable транспорти (изискват obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Предупреждение: meek_lite мостовета са много скъпи за проекта Тор. <br> <br> Използвайте ги само, ако не можете да се свържете пряко чрез obfs4 транспорти или други нормални мостове с Тор.",
+ "gui_settings_tor_bridges_custom_radio_option": "Използвайте персонализирани мостове",
+ "gui_settings_tor_bridges_custom_label": "Може да намерите мостове на <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Нито един от добавените от Вас мостове работят.\nПроверете ги отново или добавете други.",
+ "gui_settings_button_save": "Запазване",
+ "gui_settings_button_cancel": "Отказ",
+ "gui_settings_button_help": "Помощ",
+ "gui_settings_shutdown_timeout_checkbox": "Използвайте автоматично спиращия таймер",
+ "gui_settings_shutdown_timeout": "Спри дела на:",
+ "settings_error_unknown": "Не мога да се свържа с Тор контролера, защото Вашите настройки не правят смисъл.",
+ "settings_error_automatic": "Не мога да се свържа с Тор контролера. Стартиран ли е Тор браузерът във фонов режим (достъпен от torproject. org)?",
+ "settings_error_socket_port": "Не мога да се свържа с Тор контролера в {}:{}.",
+ "settings_error_socket_file": "Не мога да се свържа с Тор контролера, използвайки сокет файл {}.",
+ "settings_error_auth": "Свързан с {}:{}, но не може да се идентифицира. Може би това не е Тор контролер?",
+ "settings_error_missing_password": "Свързан с Тор контролер, но той изисква парола за идентификация.",
+ "settings_error_unreadable_cookie_file": "Свързан с Тор контролер, но паролата може да е грешна, или на Вашият потребител да не е позволено да чете бисквитката файл.",
+ "settings_error_bundled_tor_not_supported": "Използване на Тор версия, идваща с OnionShare не работи в режим на разработчик под Windows или macOS.",
+ "settings_error_bundled_tor_timeout": "Oтнема прекалено дълго време да се свържа с Тор. Може би не сте свързани с интернет или системният часовник е неточен?",
+ "settings_error_bundled_tor_broken": "OnionShare не можа да се свърже с Тор във фонов режим:\n{}",
+ "settings_test_success": "Свързан с Тор контролер.\n\nТор версия: {}\nПоддържа ephemeral onion services: {}\nПоддържа клиент автентикация: {}\nПоддържа следваща генерация .onion адреси: {}",
+ "error_tor_protocol_error": "Станала е грешка с Тор: {}",
+ "error_tor_protocol_error_unknown": "Имаше неизвестена грешка с Тор",
+ "error_invalid_private_key": "Този тип частен ключ е неподдържан",
+ "connecting_to_tor": "Свързване към Тор мрежата",
+ "update_available": "Има нов OnionShare. <a href='{}'>Кликнете тук</a>, за да го изтеглите.<br><br>Вие използвате {}, а последният е {}.",
+ "update_error_check_error": "Не мога да проверя за нови версии: OnionShare сайтът казва, че не разпознава последната версия '{}'…",
+ "update_error_invalid_latest_version": "Не мога да проверя за нова версия: Може би не сте свързани към Тор или OnionShare уебсайтът е изключен?",
+ "update_not_available": "Вие изпозвате псоледната версия на OnionShare.",
+ "gui_tor_connection_ask": "Отворете настройките, за да възстановите връзката с Тор?",
+ "gui_tor_connection_ask_open_settings": "Да",
+ "gui_tor_connection_ask_quit": "Изход",
+ "gui_tor_connection_error_settings": "Опитайте се да промените в настройките как OnionShare се свързва с Тор.",
+ "gui_tor_connection_canceled": "Не може да се установи връзка с Тор.\n\nУверете се, че имате връзка с интернтет, след което отново отворете OnionShare и пренастройте връзката с Тор.",
+ "gui_tor_connection_lost": "Връзката с Тор е прекъсната.",
+ "gui_server_started_after_timeout": "Автоматично спиращият таймер спря преди сървърът да стартира.\nМоля направете нов дял.",
+ "gui_server_timeout_expired": "Автоматично спиращият таймер спря.\nМоля актуализирайте за да започнете споделяне.",
+ "share_via_onionshare": "Споделете го чрез OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Използвайте стари адреси",
+ "gui_save_private_key_checkbox": "Използвайте постоянни адреси (стари)",
+ "gui_share_url_description": "<b>Всеки</b> с този OnionShare адрес може да <b>свали</b> Вашите файлове използвайки <b>Тор браузера</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Всеки</b> с този OnionShare адрес може да <b>качи</b> файлове на Вашия компютър, използвайки <b>Тор браузера</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Този дял няма да спре автоматично.<br><br>Всеки следващ дял ще използва повторно адреса. (За да използвате еднократни адреси, изключете \"Използвайте постоянен адрес\" в настройките)",
+ "gui_url_label_stay_open": "Този дял няма да спре автоматично.",
+ "gui_url_label_onetime": "Този дял ще спре след първото изпълнение.",
+ "gui_url_label_onetime_and_persistent": "Този дял няма да спре автоматично.<br><br>Всеки следващ дял ще използва повторно адреса. (За да използвате еднократни адреси, изключете \"Използвайте постоянен адрес\" в настройките)",
+ "gui_status_indicator_share_stopped": "Готово за споделяне",
+ "gui_status_indicator_share_working": "Започва…",
+ "gui_status_indicator_share_started": "Споделяне",
+ "gui_status_indicator_receive_stopped": "Готово за приемане",
+ "gui_status_indicator_receive_working": "Започва…",
+ "gui_status_indicator_receive_started": "Получаване",
+ "gui_file_info": "{} файлове, {}",
+ "gui_file_info_single": "{} файл, {}",
+ "history_in_progress_tooltip": "{} е в прогрес",
+ "history_completed_tooltip": "{} завършено",
+ "info_in_progress_uploads_tooltip": "{} качване(та) в прогрес",
+ "info_completed_uploads_tooltip": "{} ъплоудът(ите) е(са) завършен(и)",
+ "error_cannot_create_downloads_dir": "Не мога да създам папка за режим на приемане: {}",
+ "receive_mode_downloads_dir": "Документи, изпратени до Вас, се появяват в тази папка: {}",
+ "receive_mode_warning": "Предупреждение: Режим на приемане позволява на хора да качват файлове на Вашия компютър. Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.",
+ "gui_receive_mode_warning": "Режим на приемане позволява на хора да качват файлове на Вашия компютър.<br><br><b>Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.</b>",
+ "receive_mode_upload_starting": "Ъплоуд на общ размер {} започва",
+ "receive_mode_received_file": "Получено: {}",
+ "gui_mode_share_button": "Споделяне на файлове",
+ "gui_mode_receive_button": "Получете документи",
+ "gui_settings_receiving_label": "Настройки на получаване",
+ "gui_settings_downloads_label": "Запазете файлове в",
+ "gui_settings_downloads_button": "Разглеждане",
+ "gui_settings_public_mode_checkbox": "Публичен режим",
+ "systray_close_server_title": "OnionShare сървърът приключи",
+ "systray_close_server_message": "Един ползвател затвори сървъра",
+ "systray_page_loaded_title": "OnionShare страницата е заредена",
+ "systray_download_page_loaded_message": "Един ползвател зареди свалената страница",
+ "systray_upload_page_loaded_message": "Един ползвател зареди ъплоуд страницата",
+ "gui_uploads": "Ъплоуд история",
+ "gui_no_uploads": "Все още няма качвания",
+ "gui_clear_history": "Изтрий всичко",
+ "gui_upload_in_progress": "Качването започна",
+ "gui_upload_finished_range": "Качен {} на {}",
+ "gui_upload_finished": "Качен {}",
+ "gui_download_in_progress": "Изтеглянето започна {}",
+ "gui_open_folder_error_nautilus": "Не мога да отворя папка, защото \"nautilus\" не е на разположение. Файлът е тук: {}",
+ "gui_settings_language_label": "Предпочитан език",
+ "gui_settings_language_changed_notice": "За да влезе промяна Ви на език в сила, рестартирайте OnionShare."
+}
diff --git a/share/locale/bn.json b/share/locale/bn.json
new file mode 100644
index 00000000..691c5047
--- /dev/null
+++ b/share/locale/bn.json
@@ -0,0 +1,204 @@
+{
+ "config_onion_service": "{0:d} পোর্টে onion সার্ভিস সেটাপ করা হচ্ছে।",
+ "preparing_files": "ফাইলগুলোকে কমপ্রেস করা হচ্ছে।",
+ "give_this_url": "প্রাপককে এই এড্রেসটি দিন:",
+ "give_this_url_stealth": "প্রাপককে এই এড্রেস এবং HidServAuth লাইনটি দিন:",
+ "give_this_url_receive": "প্রেরককে এই ঠিকানাটি দিন:",
+ "give_this_url_receive_stealth": "প্রেরককে এই এড্রেস এবং HidServAuthটি দিন:",
+ "ctrlc_to_stop": "সার্ভারটি বন্ধ করার জন্য Ctrl+C চাপুন",
+ "not_a_file": "{0:s} ফাইলটি বৈধ নয়।",
+ "not_a_readable_file": "{0:s} ফাইলটি পড়া যাচ্ছে না।",
+ "no_available_port": "Onion সার্ভিস চালু করার জন্য কোন পোর্ট পাওয়া যাচ্ছে না",
+ "other_page_loaded": "এড্রেস লোড হয়েছে",
+ "close_on_timeout": "বন্ধ করা হয়েছে কারণ অটো-স্টপ টাইমার এর সময় শেষ",
+ "closing_automatically": "ট্রান্সফার শেষ তাই থেমে যাওয়া হলো",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "সতর্কতা: বড় ফাইল পাঠাতে গেলে কয়েক ঘণ্টা লাগতে পারে",
+ "systray_menu_exit": "প্রস্থান করুন",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "Tor ব্যবহার করবে না (শুধুমাত্র ডেভেলপারদের জন্য)",
+ "help_stay_open": "ফাইলগুলো পাঠানো হয়ে গেলেও শেয়ার করা থামিও না",
+ "help_shutdown_timeout": "নির্দিষ্ট সেকেন্ডের পর শেয়ার করা বন্ধ করে দিও",
+ "help_stealth": "ক্লায়েন্ট অনুমোদন ব্যবহার করুন (উন্নততর)",
+ "help_receive": "কোনকিছু শেয়ার না করে শুধু গ্রহণ করবে",
+ "help_debug": "OnionShare-এর এররগুলো stdout-এ দেখাও, আর ওয়েব এররগুলো ডিস্কে লগ করো",
+ "help_filename": "শেয়ার করার জন্য ফাইল বা ফোল্ডারের লিস্ট",
+ "help_config": "কাস্টম JSON কনফিগারেশন ফাইলের লোকেশন (যদি থাকে)",
+ "gui_drag_and_drop": "শেয়ার করা শুরু করতে\nফাইল এবং ফোল্ডারগুলো ড্র্যাগ করে ড্রপ করুন",
+ "gui_add": "সংযোজন করুন",
+ "gui_delete": "মুছ",
+ "gui_choose_items": "পছন্দ করুন",
+ "gui_share_start_server": "শেয়ার করা শুরু করো",
+ "gui_share_stop_server": "শেয়ার করা বন্ধ করো",
+ "gui_share_stop_server_shutdown_timeout": "শেয়ার করা বন্ধ করো ({} সেকেন্ড বাকি)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "টাইমার অনুযায়ী অটোমেটিক বন্ধ হবে {}-তে",
+ "gui_receive_start_server": "প্রাপ্ত মোড আরম্ভ করুন ",
+ "gui_receive_stop_server": "প্রাপ্ত মোড বন্ধ করুন ",
+ "gui_receive_stop_server_shutdown_timeout": "প্রাপ্ত মোড বন্ধ করুন ({}সে বাকি) ",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "টাইমার অনুযায়ী অটোমেটিক বন্ধ হবে {}-তে",
+ "gui_copy_url": "এড্রেস কপি করো",
+ "gui_copy_hidservauth": "HidServAuth কপি করো",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "বাতিল করা হয়েছে",
+ "gui_copied_url_title": "OnionShare এড্রেস কপি করা হয়েছে",
+ "gui_copied_url": "OnionShare এড্রেসটি ক্লিপবোর্ডে কপি করা হয়েছে",
+ "gui_copied_hidservauth_title": "HidServAuth কপি করা হয়েছে",
+ "gui_copied_hidservauth": "HidServAuth লাইনটি ক্লিপবোর্ডে কপি করা হয়েছে",
+ "gui_please_wait": "চালু করা হচ্ছে… বন্ধ করার জন্য এখানে ক্লিক করুন।",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "OnionShare (অনিয়নশেয়ার) {0:s} | https://onionshare.org/",
+ "gui_quit_title": "একটু দাড়ান",
+ "gui_share_quit_warning": "আপনি ফাইল পাঠানোর মধ্যে আছেন। আপনি কি আসলেই OnionShare বন্ধ করতে চান?",
+ "gui_receive_quit_warning": "আপনি ফাইল গ্রহণের মধ্যে আছেন। আপনি কি আসলেই OnionShare বন্ধ করতে চান?",
+ "gui_quit_warning_quit": "প্রস্থান করুন",
+ "gui_quit_warning_dont_quit": "বাতিল",
+ "error_rate_limit": "কেউ একজন আপনার এড্রেসটিতে অসফলভাবে এক্সেস করার চেষ্টা করেছে, এর মানে তারা আপনার এড্রেসটি আন্দাজ করার চেষ্টা করছে, তাই OnionShare নিরাপত্তার জন্য সার্ভার বন্ধ করে দিয়েছে। নতুন করে শেয়ার করা শুরু করুন এবং প্রাপককে নতুন এড্রেসটি দিন।",
+ "zip_progress_bar_format": "কমপ্রেস করা হচ্ছে: %p%",
+ "error_stealth_not_supported": "ক্লায়েন্ট অথোরাইজেশন ব্যবহার করার জন্য, আপনার অন্তত Tor 0.2.9.1-alpha (or Tor Browser 6.5) এবং python3-stem 1.5.0 দুটোই থাকতে হবে।",
+ "error_ephemeral_not_supported": "OnionShare ব্যবহার করার জন্য Tor 0.2.9.1-alpha (or Tor Browser 6.5) এবং python3-stem 1.5.0 দুটোই থাকতে হবে।",
+ "gui_settings_window_title": "সেটিং",
+ "gui_settings_whats_this": "<a href='{0:s}'>এটা কি?</a>",
+ "gui_settings_stealth_option": "ক্লায়েন্ট অথোরাইজেশন ব্যবহার করো",
+ "gui_settings_stealth_hidservauth_string": "আপনার প্রাইভেট কি সেভ করে থাকলে, এর মানে হলো এখন আপনি আপনার HidServAuth কপি করার জন্য ক্লিক করতে পারেন।",
+ "gui_settings_autoupdate_label": "নতুন ভার্সন এসেছে কিনা দেখো",
+ "gui_settings_autoupdate_option": "নতুন ভার্সন আসলে আমাকে জানাবে",
+ "gui_settings_autoupdate_timestamp": "সবশেষ চেক করা হয়েছে: {}",
+ "gui_settings_autoupdate_timestamp_never": "কখনো নয়",
+ "gui_settings_autoupdate_check_button": "নতুন ভার্সন এসেছে কিনা দেখো",
+ "gui_settings_general_label": "সাধারণ সেটিংস",
+ "gui_settings_sharing_label": "শেয়ারের জন্য সেটিংস",
+ "gui_settings_close_after_first_download_option": "ফাইল পাঠানো হলে শেয়ার করা বন্ধ করে দিও",
+ "gui_settings_connection_type_label": "OnionShare কিভাবে Tor-এ কানেক্ট করবে?",
+ "gui_settings_connection_type_bundled_option": "OnionShare-এর নিজস্ব Tor ভার্সনটি ব্যবহার করো",
+ "gui_settings_connection_type_automatic_option": "Tor Browser থেকে অটোমেটিক কনফিগার করার চেষ্টা করো",
+ "gui_settings_connection_type_control_port_option": "কন্ট্রোল পোর্ট ব্যবহার করে কানেক্ট করো",
+ "gui_settings_connection_type_socket_file_option": "সকেট ফাইল দিয়ে কানেক্ট করো",
+ "gui_settings_connection_type_test_button": "Tor-এর সাথে কানেকশন পরীক্ষা করো",
+ "gui_settings_control_port_label": "নিয়ন্ত্রন পোর্ট",
+ "gui_settings_socket_file_label": "সকেট ফাইল",
+ "gui_settings_socks_label": "SOCKS পোর্ট",
+ "gui_settings_authenticate_label": "Tor অথেনটিকেশন সেটিংস",
+ "gui_settings_authenticate_no_auth_option": "অথেনটিকেশন ছাড়া, বা কুকি অথেনটিকেশন",
+ "gui_settings_authenticate_password_option": "পাসওয়ার্ড",
+ "gui_settings_password_label": "পাসওয়ার্ড",
+ "gui_settings_tor_bridges": "Tor ব্রিজ সাপোর্ট",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "ব্রিজ ব্যবহার করো না",
+ "gui_settings_tor_bridges_obfs4_radio_option": "নিজস্ব obfs4 প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "নিজস্ব obfs4 প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো (obfs4proxy লাগবে)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "নিজস্ব meek_lite (Azure) প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "নিজস্ব meek_lite (Azure) প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো (obfs4proxy লাগবে)",
+ "gui_settings_meek_lite_expensive_warning": "সতর্কতা: meek_lite ব্রিজ পরিচালনা করা Tor Project-এর জন্য অনেক ব্যয়বহুল।<br><br>এগুলো তখনই ব্যবহার করুন যখন Tor-এ সরাসরি কানেক্ট করতে পারছেন না, obfs4 ট্রান্সপোর্ট দিয়ে, অথবা অন্যান্য সাধারণ ব্রিজ দিয়ে।",
+ "gui_settings_tor_bridges_custom_radio_option": "কাস্টম ব্রিজ ব্যবহার করো",
+ "gui_settings_tor_bridges_custom_label": "ব্রিজ পেতে চাইলে <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a> দেখুন",
+ "gui_settings_tor_bridges_invalid": "আপনার দেয়া কোন ব্রিজই কাজ করছে না।\nআরেকবার চেক করে দেখুন বা নতুন ব্রিজ দিয়ে চেষ্টা করুন।",
+ "gui_settings_button_save": "সেভ",
+ "gui_settings_button_cancel": "বাতিল",
+ "gui_settings_button_help": "সাহায্য",
+ "gui_settings_shutdown_timeout_checkbox": "কানেকশন বন্ধ করার জন্য অটোমেটিক টাইমার ব্যবহার করো",
+ "gui_settings_shutdown_timeout": "শেয়ার বন্ধ করুন:",
+ "settings_error_unknown": "টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারে না কারণ আপনার বিন্যাসনসমূহ বোধগম্য নয় । ",
+ "settings_error_automatic": "টর নিয়ন্ত্রকের সাথে সংযোগ স্থাপন করা যায়নি । টর ব্রাউজার (torproject.org থেকে পাওয়া যায়) ব্রাকগ্রাউন চলমান?",
+ "settings_error_socket_port": "{}: {} এ টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারছি না । ",
+ "settings_error_socket_file": "সকেট ফাইল {} ব্যবহার করে টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারে না । ",
+ "settings_error_auth": "{}: {}-এর সাথে সংযুক্ত, কিন্তু পরীক্ষা করা যাচ্ছে না । হয়তো এটা কোন টর নিয়ন্ত্রক নয়? ",
+ "settings_error_missing_password": "টর কন্ট্রোলার সাথে সংযুক্ত, কিন্তু তা প্রমাণীকরণ একটি পাসওয়ার্ড প্রয়োজন.",
+ "settings_error_unreadable_cookie_file": "টর নিয়ন্ত্রকের সাথে সংযুক্ত, কিন্তু পাসওয়ার্ড ভুল হতে পারে, অথবা আপনার ব্যবহারকারীকে কুকি ফাইলে পড়ার অনুমতি দেওয়া হয়নি। ",
+ "settings_error_bundled_tor_not_supported": "OnionShare এর সাথে আসা টর সংস্করণটি ব্যবহার করে উইন্ডোজ বা ম্যাকোসে ডেভেলপার মোডে কাজ করে না।",
+ "settings_error_bundled_tor_timeout": "টর সাথে সংযোগ করার জন্য খুব বেশি সময় লাগছে। হয়তো আপনি ইন্টারনেটের সাথে সংযুক্ত নন, অথবা একটি ভুল সিস্টেম ঘড়ি আছে?",
+ "settings_error_bundled_tor_broken": "ব্যাকগ্রাউন্ডে OnionShare টর এর সাথে সংযুক্ত নয়:\n\n\n{}",
+ "settings_test_success": "টর কন্ট্রোলার এর সঙ্গে যুক্ত হয়েছে ।\n\nটর সংস্করণ: {}\n\nOnion Services সেবা সমর্থন করে: {}.\n\nক্লায়েন্ট প্রমাণীকরণ সমর্থন করে: {}.\n\nnext-gen .onion ঠিকানাগুলো সমর্থন করে: {} । ",
+ "error_tor_protocol_error": "টর-এ একটি ত্রুটি ছিল: {} ",
+ "error_tor_protocol_error_unknown": "টর-এ একটি অজানা ত্রুটি আছে",
+ "error_invalid_private_key": "এই ব্যক্তিগত কী ধরন টি অসমর্থিত ",
+ "connecting_to_tor": "টর নেটওয়ার্কে সংযুক্ত হচ্ছে ",
+ "update_available": "",
+ "update_error_check_error": "নতুন সংস্করণের জন্য পরীক্ষা করা যায়নি: onionshare ওয়েবসাইট বলছে সাম্প্রতিক সংস্করণটি হচ্ছে অস্বীকৃত ' {} '...",
+ "update_error_invalid_latest_version": "নতুন সংস্করণের জন্য পরীক্ষা করা যায়নি: হয়তো আপনি টর-এর সাথে সংযুক্ত নন, অথবা OnionShare ওয়েবসাইট বন্ধ আছে?",
+ "update_not_available": "আপনি সর্বশেষ OnionShare চালাচ্ছেন ।",
+ "gui_tor_connection_ask": "টর থেকে সংযোগ সাজাতে সেটিংস খুলুন?",
+ "gui_tor_connection_ask_open_settings": "হ্যাঁ",
+ "gui_tor_connection_ask_quit": "প্রস্থান করুন",
+ "gui_tor_connection_error_settings": " কিভাবে onionshare সেটিংসে টর নেটওয়ার্ক সংযোগ করে পরিবর্তন করতে চেষ্টা করুন ।",
+ "gui_tor_connection_canceled": "টর-এ সংযোগ করা যায়নি ।\n\nআপনি ইন্টারনেটের সাথে সংযুক্ত আছেন কিনা তা নিশ্চিত করুন, তারপর onionshare পুনরায় খুলুন এবং টর এর সংযোগটি সেট আপ করুন । ",
+ "gui_tor_connection_lost": "টর থেকে বিচ্ছিন্ন । ",
+ "gui_server_started_after_timeout": "সার্ভার শুরু হওয়ার আগেই অটো স্টপ টাইমার শেষ হয়ে যায় ।\n\nঅনুগ্রহ করে একটি নতুন শেয়ার তৈরি করুন. ",
+ "gui_server_timeout_expired": "অটো-স্টপ টাইমার ইতিমধ্যেই শেষ হয়ে গিয়েছে ।\n\nঅনুগ্রহ করে শেয়ারিং শুরু করতে এটি আপডেট করুন. ",
+ "share_via_onionshare": "এটি OnionShare ",
+ "gui_use_legacy_v2_onions_checkbox": "লিগ্যাসি ঠিকানাগুলি ব্যবহার করুন ",
+ "gui_save_private_key_checkbox": "একটি অবিরাম ঠিকানা ব্যবহার করুন ",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "এই শেয়ারটি অটো-স্টপ হবে না । ",
+ "gui_url_label_onetime": "এই শেয়ারটি প্রথম সমাপ্তির পরে বন্ধ হবে. ",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "শেয়ার করার জন্য প্রস্তুত ",
+ "gui_status_indicator_share_working": "শুরু...",
+ "gui_status_indicator_share_started": "শেয়ারিং",
+ "gui_status_indicator_receive_stopped": "পাওয়ার জন্য প্রস্তুত ",
+ "gui_status_indicator_receive_working": "শুরু... ",
+ "gui_status_indicator_receive_started": "গ্রহণ",
+ "gui_file_info": "{} ফাইল, {}",
+ "gui_file_info_single": "{} ফাইল, {}",
+ "history_in_progress_tooltip": "{} অগ্রসর হচ্ছে ",
+ "history_completed_tooltip": "{} সম্পূর্ণ\n",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "সতর্কীকরণ: প্রাপ্ত মোড লোকজনকে আপনার কম্পিউটারে ফাইল আপলোড করতে দেয় । আপনি যদি তাদের খোলেন তবে কিছু ফাইল সম্ভবত আপনার কম্পিউটারের নিয়ন্ত্রণ নিতে পারে । শুধুমাত্র আপনি যে ব্যক্তিদের বিশ্বাস করেন, অথবা আপনি যদি জানেন আপনি কি করছেন তা শুধুমাত্র খোলা জিনিস । ",
+ "gui_receive_mode_warning": "গ্রহণ মোডে লোকজন আপনার কম্পিউটারে ফাইলগুলো আপলোড করতে দেয় । <br><br><b>আপনি যদি তাদের খোলেন তবে কিছু ফাইল সম্ভবত আপনার কম্পিউটারের নিয়ন্ত্রণ নিতে পারে । শুধুমাত্র আপনি যে ব্যক্তিদের বিশ্বাস করেন, অথবা আপনি যদি জানেন আপনি কি করছেন তা শুধুমাত্র খোলা জিনিস ।</b>",
+ "receive_mode_upload_starting": "মোট আকারের {} টি আপলোড শুরু হচ্ছে ",
+ "receive_mode_received_file": "প্রাপ্ত: {} ",
+ "gui_mode_share_button": "ফাইলগুলো শেয়ার করুন ",
+ "gui_mode_receive_button": "ফাইল গ্রহণ করা হচ্ছে ",
+ "gui_settings_receiving_label": "সেটিংস গ্রহণ করা হচ্ছে ",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "দেখা",
+ "gui_settings_public_mode_checkbox": "সর্বজনীন মোড ",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "পৃষ্ঠা লোড করা হয়েছে ",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "ফোল্ডার খোলা যাচ্ছে না কারণ nautilus বিদ্যমান নয় । ফাইলটি এখানে: {} ",
+ "gui_settings_language_label": "পছন্দনীয় ভাষা ",
+ "gui_settings_language_changed_notice": "আপনার ভাষার পরিবর্তন প্রভাব বিস্তার করার জন্য OnionShare পুনর্সূচনা করুন. ",
+ "gui_add_files": "ফাইল যোগ করো",
+ "gui_add_folder": "ফোল্ডার যোগ করো",
+ "gui_settings_onion_label": "Onion সেটিংস",
+ "gui_connect_to_tor_for_onion_settings": "onion সেবা সেটিংস দেখতে টর এর সাথে সংযোগ করুন ",
+ "error_cannot_create_data_dir": "onionshare ডাটা ফোল্ডার তৈরি করা যায়নি: {} ",
+ "receive_mode_data_dir": "আপনার কাছে পাঠানো ফাইলসমূহ এই ফোল্ডারে প্রদর্শিত হয়েছে: {} ",
+ "gui_settings_data_dir_label": "ফাইল সংরক্ষণ করুন ",
+ "gui_settings_data_dir_browse_button": "ব্রাউজ",
+ "systray_page_loaded_message": "onionshare ঠিকানা লোড করা হয়েছে ",
+ "systray_share_started_title": "শেয়ারিং শুরু করা হয়েছে",
+ "systray_share_completed_title": "শেয়ারিং সম্পূর্ণ হয়েছে",
+ "systray_share_completed_message": "ফাইল পাঠানো শেষ হয়েছে",
+ "systray_share_canceled_title": "শেয়ারকরণ বাতিল করা হয়েছে",
+ "systray_share_canceled_message": "কেউ আপনার ফাইল গ্রহণ করা বাতিল করেছে",
+ "systray_receive_started_title": "গ্রহণ শুরু হয়েছে",
+ "systray_receive_started_message": "কেউ আপনার কাছে ফাইল পাঠাচ্ছে",
+ "gui_all_modes_history": "ইতিহাস",
+ "gui_all_modes_clear_history": "সব পরিষ্কার করুন",
+ "gui_all_modes_transfer_started": "{} শুরু হয়েছে"
+}
diff --git a/share/locale/ca.json b/share/locale/ca.json
new file mode 100644
index 00000000..a59dae2a
--- /dev/null
+++ b/share/locale/ca.json
@@ -0,0 +1,217 @@
+{
+ "config_onion_service": "S'està establint el servei onion al port {0:d}.",
+ "preparing_files": "S'estan comprimint els arxius.",
+ "give_this_url": "Dóna aquesta adreça a la persona destinatària:",
+ "give_this_url_stealth": "Fes arribar aquestes dues línies a la/es persona/es destinatària/es:",
+ "give_this_url_receive": "Dóna aquesta adreça a la persona remitent:",
+ "give_this_url_receive_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona remitent:",
+ "ctrlc_to_stop": "Prem Control+C per aturar el servidor",
+ "not_a_file": "{0:s} no és un arxiu vàlid.",
+ "not_a_readable_file": "{0:s} no és un arxiu llegible.",
+ "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei onion",
+ "other_page_loaded": "Adreça carregada",
+ "close_on_timeout": "S'ha aturat perquè s'ha acabat el temps d'espera",
+ "closing_automatically": "S'ha aturat perquè ha acabat la transferència",
+ "timeout_download_still_running": "S'està esperant que acabi la descàrrega",
+ "large_filesize": "Compte: La transferència d'arxius molt grans podria trigar hores",
+ "systray_menu_exit": "Surt",
+ "systray_download_started_title": "S'ha iniciat la descàrrega amb OnionShare",
+ "systray_download_started_message": "Algú ha començat a descarregar els teus arxius",
+ "systray_download_completed_title": "S'ha completat la descàrrega amb OnionShare",
+ "systray_download_completed_message": "Algú ha acabat de descarregar els teus arxius",
+ "systray_download_canceled_title": "S'ha canceŀlat la descàrrega",
+ "systray_download_canceled_message": "L'usuari va cancel·lar la descàrrega",
+ "systray_upload_started_title": "S'ha iniciat la pujada",
+ "systray_upload_started_message": "Algú ha començat a pujar arxius al teu ordinador",
+ "help_local_only": "No facis servir Tor (només per a desenvolupament)",
+ "help_stay_open": "Mantingues obert el servei després d'enviar els arxius",
+ "help_shutdown_timeout": "Deixa de compartir al cap de tants segons",
+ "help_stealth": "Fes servir autorització de client (avançat)",
+ "help_receive": "Rep recursos en comptes d'enviar-los",
+ "help_debug": "Envia els errors d'OnionShare a stdout i els errors web al disc",
+ "help_filename": "Llista d'arxius o carpetes a compartir",
+ "help_config": "Ubicació de la configuració JSON personalitzada",
+ "gui_drag_and_drop": "Arrossega arxius i carpetes\nper començar a compartir",
+ "gui_add": "Afegeix",
+ "gui_delete": "Esborra",
+ "gui_choose_items": "Escull",
+ "gui_share_start_server": "Comparteix",
+ "gui_share_stop_server": "Deixa de compartir",
+ "gui_share_stop_server_shutdown_timeout": "Deixa de compartir (queden {}s)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}",
+ "gui_receive_start_server": "Inicia en mode de recepció",
+ "gui_receive_stop_server": "Atura el mode de recepció",
+ "gui_receive_stop_server_shutdown_timeout": "Atura el mode de recepció (queden {}s)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}",
+ "gui_copy_url": "Copia l'adreça",
+ "gui_copy_hidservauth": "Copia el HidServAuth",
+ "gui_downloads": "Historial de descàrregues",
+ "gui_no_downloads": "No n'hi ha cap",
+ "gui_canceled": "Canceŀlat",
+ "gui_copied_url_title": "S'ha copiat l'adreça OnionShare",
+ "gui_copied_url": "S'ha copiat l'adreça OnionShare al porta-retalls",
+ "gui_copied_hidservauth_title": "S'ha copiat el HidServAuth",
+ "gui_copied_hidservauth": "S'ha copiat la línia HidServAuth al porta-retalls",
+ "gui_please_wait": "S'està iniciant… Clica per a canceŀlar.",
+ "gui_download_upload_progress_complete": "Han passat %p%, {0:s}.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (s'està calculant)",
+ "gui_download_upload_progress_eta": "{0:s}, temps restant: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Espera un moment",
+ "gui_share_quit_warning": "Encara s'estan enviant fitxers. Segur que vols sortir de l'OnionShare?",
+ "gui_receive_quit_warning": "Encara s'estan rebent arxius. Segur que vols sortir de l'OnionShare?",
+ "gui_quit_warning_quit": "Surt",
+ "gui_quit_warning_dont_quit": "Canceŀla",
+ "error_rate_limit": "Algú ha fet massa intents a la teva adreça, cosa que podria significar que l'estan intentant endevinar. Per això OnionShare s'ha aturat sola. Pots tornar a començar i enviar a la destinatària la nova adreça.",
+ "zip_progress_bar_format": "S'està comprimint: %p%",
+ "error_stealth_not_supported": "Per fer servir l'autorització de client, necessites les versions iguals o superiors a Tor 0.2.9.1-alpha (o Tor Browser 6.5) i python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare necessita almenys les versions Tor 0.2.7.1 i python3-stem 1.4.0.",
+ "gui_settings_window_title": "Configuració",
+ "gui_settings_whats_this": "<a href='{0:s}'>Què és això?</a>",
+ "gui_settings_stealth_option": "Fes servir autorització de client",
+ "gui_settings_stealth_hidservauth_string": "Ara que ja has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu \"HidServAuth\".",
+ "gui_settings_autoupdate_label": "Comprova si hi ha noves versions",
+ "gui_settings_autoupdate_option": "Notifica'm si hi ha una actualització disponible",
+ "gui_settings_autoupdate_timestamp": "Última comprovació: {}",
+ "gui_settings_autoupdate_timestamp_never": "Mai",
+ "gui_settings_autoupdate_check_button": "Comprova si hi ha una versió més nova",
+ "gui_settings_general_label": "Configuració general",
+ "gui_settings_sharing_label": "Configuració de compartir",
+ "gui_settings_close_after_first_download_option": "Deixa de compartir després d'enviar arxius",
+ "gui_settings_connection_type_label": "Com hauria de connectar-se OnionShare a Tor?",
+ "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare",
+ "gui_settings_connection_type_automatic_option": "Intenta la configuració automàtica amb el Navegador Tor",
+ "gui_settings_connection_type_control_port_option": "Connecta fent servir el port de control",
+ "gui_settings_connection_type_socket_file_option": "Connecta fent servir un arxiu de socket",
+ "gui_settings_connection_type_test_button": "Comprova la connexió a Tor",
+ "gui_settings_control_port_label": "Port de control",
+ "gui_settings_socket_file_label": "Arxiu de socket",
+ "gui_settings_socks_label": "Port SOCKS",
+ "gui_settings_authenticate_label": "Configuració d'autenticació a Tor",
+ "gui_settings_authenticate_no_auth_option": "Sense autenticació o autenticació per cookies",
+ "gui_settings_authenticate_password_option": "Contrasenya",
+ "gui_settings_password_label": "Contrasenya",
+ "gui_settings_tor_bridges": "Ponts de Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "No facis servir ponts",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Fes servir el transport connectable obfs4",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Fes servir el transport connectable obfs4 (necessita obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Fes servir el transport connectable meek_lite (Azure)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Fes servir el transport connectable meek_lite (Azure, necessita obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Compte: els ponts meek_lite costen molts recursos al Tor Project per funcionar.<br><br>Sisplau, fes-los servir només si no pots connectar-te a Tor directament, a través de obfs4, o a través de ponts normals.",
+ "gui_settings_tor_bridges_custom_radio_option": "Fes servir ponts personalitzats",
+ "gui_settings_tor_bridges_custom_label": "Pots trobar-ne a <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Cap dels ponts que has afegit ha funcionat.\nComprova'ls o prova d'afegir-ne de nous.",
+ "gui_settings_button_save": "Desa",
+ "gui_settings_button_cancel": "Canceŀla",
+ "gui_settings_button_help": "Ajuda",
+ "gui_settings_shutdown_timeout_checkbox": "Posa un temporitzador d'aturada",
+ "gui_settings_shutdown_timeout": "Atura a:",
+ "settings_error_unknown": "No s'ha pogut connectar a Tor perquè la configuració és inconsistent.",
+ "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a torproject.org)",
+ "settings_error_socket_port": "No s'ha pogut establir la connexió al controlador de Tor a {}:{}.",
+ "settings_error_socket_file": "No s'ha pogut connectar al controlador de Tor fent servir el fitxer de socket {}.",
+ "settings_error_auth": "S'ha establert la connexió a {}:{} però ha fallat l'autenticació. Pot ser que no sigui un controlador de Tor?",
+ "settings_error_missing_password": "S'ha establer la connexió al controlador de Tor, però necessita una contrasenya d'autenticació.",
+ "settings_error_unreadable_cookie_file": "S'ha establert la connexió al controlador de Tor, però hi ha hagut un error de permisos. Pot ser que la contrasenya sigui errònia o que faltin permisos de lectura a l'arxiu de cookie.",
+ "settings_error_bundled_tor_not_supported": "La versió de Tor inclosa a OnionShare no funciona en mode de desenvolupador a Windows ni MacOS.",
+ "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegura't que estàs connectat a internet i que tens en hora el rellotge del sistema.",
+ "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}",
+ "settings_test_success": "Connectat al controlador de Tor.\n\nVersió de Tor: {}\nSuporta serveis onion efímers: {}.\nSuporta autenticació del client: {}.\nSuporta adreces .onion de nova generació: {}.",
+ "error_tor_protocol_error": "Hi ha hagut un error amb Tor: {}",
+ "error_tor_protocol_error_unknown": "Hi ha hagut un error desconegut amb Tor",
+ "error_invalid_private_key": "Aquest tipus de clau privada no està suportat",
+ "connecting_to_tor": "Connectant a la xarxa Tor",
+ "update_available": "Ha sortit una nova versió d'OnionShare.<a href='{}'>Feu clic aquí</a> per obtenir-la.<br><br>Esteu usant {} i la més recent és {}.",
+ "update_error_check_error": "No s'ha pogut comprovar si hi ha versions més noves. La web d'OnionShare diu que l'última versió és '{}' però no s'ha pogut reconèixer…",
+ "update_error_invalid_latest_version": "No s'ha pogut comprovar si hi ha una versió més nova. Pot ser que no estiguis connectat/da a Tor o que la web d'OnionShare estigui caiguda?",
+ "update_not_available": "Aquesta és la versió més nova d'OnionShare.",
+ "gui_tor_connection_ask": "Vols anar a la configuració per provar d'arreglar la connexió a Tor?",
+ "gui_tor_connection_ask_open_settings": "Sí",
+ "gui_tor_connection_ask_quit": "Surt",
+ "gui_tor_connection_error_settings": "Prova de canviar la configuració de com OnionShare es connecta a la xarxa Tor.",
+ "gui_tor_connection_canceled": "No s'ha pogut establir la connexió amb la xarxa Tor.\n\nAssegura't que tens connexió a internet, torna a obrir OnionShare i prepara la connexió a Tor.",
+ "gui_tor_connection_lost": "S'ha perdut la connexió amb Tor.",
+ "gui_server_started_after_timeout": "El temporitzador ha acabat abans que s'iniciés el servidor.\nTorna a compartir-ho.",
+ "gui_server_timeout_expired": "El temporitzador ja s'ha acabat.\nReinicia'l per a poder compartir.",
+ "share_via_onionshare": "Comparteix-ho amb OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Fes servir adreces amb un format antic",
+ "gui_save_private_key_checkbox": "Fes servir una adreça persistent",
+ "gui_share_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>descarregar</b> arxius teus fent servir el <b>Navegador de Tor</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>pujar</b> arxius al teu ordinador fent servir el <b>Navegador de Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Aquesta sessió no es tancarà.<br><br>Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».",
+ "gui_url_label_stay_open": "Aquesta recurs no es deixarà de compartir sol.",
+ "gui_url_label_onetime": "Aquest recurs deixarà de compartir-se després de la primera descàrrega.",
+ "gui_url_label_onetime_and_persistent": "Aquest recurs no es deixarà de compartir sol.<br><br>Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».",
+ "gui_status_indicator_share_stopped": "A punt per compartir",
+ "gui_status_indicator_share_working": "S'està iniciant…",
+ "gui_status_indicator_share_started": "S'està compartint",
+ "gui_status_indicator_receive_stopped": "A punt per rebre",
+ "gui_status_indicator_receive_working": "S'està iniciant…",
+ "gui_status_indicator_receive_started": "S'està rebent",
+ "gui_file_info": "{} arxius, {}",
+ "gui_file_info_single": "{} arxiu, {}",
+ "history_in_progress_tooltip": "{} en procés",
+ "history_completed_tooltip": "{} completat/s",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "Alerta: El mode de rebuda permet a qualsevol de pujar arxius al teu ordinador. Algú amb males intencions podria pendre el control de la teva màquina si obrissis arxius maliciosos que haguessin pujat. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.",
+ "gui_receive_mode_warning": "El mode de rebuda permet a qualsevol de pujar arxius al teu ordinador.<br><br><b>Algú amb males intencions podria pendre el control de la teva màquina si et pugessin i obrissis arxius maliciosos. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.</b>",
+ "receive_mode_upload_starting": "S'està començant a rebre {}",
+ "receive_mode_received_file": "S'han rebut: {}",
+ "gui_mode_share_button": "Comparteix arxius",
+ "gui_mode_receive_button": "Rep arxius",
+ "gui_settings_receiving_label": "Configuració de rebuda",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "Mode públic",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "S'ha carregat la pàgina",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "No s'ha pogut obrir la carpeta perquè el Nautilus no està disponible. L'arxiu està a: {}",
+ "gui_settings_language_label": "Llengua preferida",
+ "gui_settings_language_changed_notice": "Reobre OnionShare perquè el canvi de llengua tingui efecte.",
+ "timeout_upload_still_running": "S'està esperant que acabi la pujada",
+ "gui_add_files": "Afegeix arxius",
+ "gui_add_folder": "Afegeix una carpeta",
+ "gui_settings_onion_label": "Servei ceba",
+ "gui_connect_to_tor_for_onion_settings": "Connecta't a Tor per configurar els serveis ocults",
+ "error_cannot_create_data_dir": "No s'ha pogut crear la carpeta de dades d'OnionShare: {}",
+ "receive_mode_data_dir": "Els arxius que rebis apareixeran aquí: {}",
+ "gui_settings_data_dir_label": "Desa els arxius a",
+ "gui_settings_data_dir_browse_button": "Explora",
+ "systray_page_loaded_message": "L'adreça d'OnionShare s'ha carregat",
+ "systray_share_started_title": "S'ha començat a compartir",
+ "systray_share_started_message": "S'està començant a enviar els arxius a algú",
+ "systray_share_completed_title": "S'ha acabat de compartir",
+ "systray_share_completed_message": "Els arxius s'han acabat d'enviar",
+ "systray_share_canceled_title": "S'ha deixat de compartir",
+ "systray_share_canceled_message": "Algú ha aturat la descàrrega que estava fent dels teus fitxers",
+ "systray_receive_started_title": "S'ha començat a rebre",
+ "systray_receive_started_message": "Algú t'està enviant arxius",
+ "gui_all_modes_history": "Historial",
+ "gui_all_modes_clear_history": "Esborra-ho tot",
+ "gui_all_modes_transfer_started": "Ha començat a {}",
+ "gui_all_modes_transfer_finished_range": "S'ha transferit entre {} i {}",
+ "gui_all_modes_transfer_finished": "Transferit a {}",
+ "gui_all_modes_transfer_canceled_range": "S'ha canceŀlat. Ha funcionat entre {} i {}",
+ "gui_all_modes_transfer_canceled": "S'ha canceŀlat {}",
+ "gui_all_modes_progress_complete": "Han passat %p%, {0:s}.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (s'està calculant)",
+ "gui_all_modes_progress_eta": "{0:s}, Temps aproximat: {1:s}, %p%",
+ "gui_share_mode_no_files": "Encara no s'han enviat fitxers",
+ "gui_share_mode_timeout_waiting": "S'està acabant d'enviar",
+ "gui_receive_mode_no_files": "Encara no s'ha rebut res",
+ "gui_receive_mode_timeout_waiting": "S'està acabant de rebre"
+}
diff --git a/share/locale/cs.json b/share/locale/cs.json
index 53525ea3..3eb03198 100644
--- a/share/locale/cs.json
+++ b/share/locale/cs.json
@@ -1,23 +1,15 @@
{
- "config_onion_service": "Nastavuji onion service na portu {0:d}.",
- "preparing_files": "Připravuji soubory ke sdílení.",
- "wait_for_hs": "Čekám na HS až bude připravena:",
- "wait_for_hs_trying": "Zkouším...",
- "wait_for_hs_nope": "Ještě nepřipraven.",
- "wait_for_hs_yup": "Připraven!",
+ "config_onion_service": "Nastavuji onion požadavky na portu {0:d}.",
+ "preparing_files": "Probíhá komprese souborů.",
"give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:",
- "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
- "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru",
- "not_a_file": "{0:s} není soubor.",
- "download_page_loaded": "Download page loaded",
+ "give_this_url_stealth": "Sděl příjemci tuto URL a HidServAuth:",
+ "ctrlc_to_stop": "Stiskněte Ctrl+C pro zastavení serveru",
+ "not_a_file": "{0:s} není platný soubor.",
"other_page_loaded": "URL loaded",
"closing_automatically": "Zastavuji automaticky, protože stahování skončilo",
"large_filesize": "Varování: Posílání velkých souborů může trvat hodiny",
- "error_tails_invalid_port": "Nesprávná hodnota, port musí být celé číslo",
- "error_tails_unknown_root": "Neznámá chyba s Tails root procesem",
"help_local_only": "Nepoužívat Tor: jen pro vývoj",
"help_stay_open": "Nechat běžet onion service po skončení stahování",
- "help_transparent_torification": "My system is transparently torified",
"help_stealth": "Create stealth onion service (advanced)",
"help_debug": "Zaznamenat chyby na disk",
"help_filename": "Seznam souborů a složek ke sdílení",
@@ -25,48 +17,37 @@
"gui_add": "Přidat",
"gui_delete": "Smazat",
"gui_choose_items": "Vybrat",
- "gui_start_server": "Spustit sdílení",
- "gui_stop_server": "Zastavit sdílení",
+ "gui_share_start_server": "Spustit sdílení",
+ "gui_share_stop_server": "Zastavit sdílení",
"gui_copy_url": "Kopírovat URL",
"gui_copy_hidservauth": "Kopírovat HidServAuth",
- "gui_downloads": "Stahování:",
+ "gui_downloads": "Historie stahování",
"gui_canceled": "Zrušeno",
"gui_copied_url": "URL zkopírováno do schránky",
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
- "gui_starting_server1": "Spouštím Tor onion service...",
- "gui_starting_server2": "Zpracovávám soubory...",
"gui_please_wait": "Prosím čekejte...",
- "error_hs_dir_cannot_create": "Nejde vytvořit složka onion service {0:s}",
- "error_hs_dir_not_writable": "nejde zapisovat do složky onion service {0:s}",
- "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
- "gui_download_progress_complete": "%p%, Uplynulý čas: {0:s}",
- "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)",
- "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
"version_string": "Onionshare {0:s} | https://onionshare.org/",
- "gui_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.",
+ "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.",
"gui_quit_warning_quit": "Zavřít",
"gui_quit_warning_dont_quit": "Zůstat",
"error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.",
"zip_progress_bar_format": "Zpracovávám soubory: %p%",
"error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
"error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.",
- "gui_menu_file_menu": "&File",
- "gui_menu_settings_action": "&Settings",
- "gui_menu_quit_action": "&Quit",
"gui_settings_window_title": "Nastavení",
"gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?",
"gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem",
- "gui_settings_connection_type_control_port_option": "Connect using control port",
- "gui_settings_connection_type_socket_file_option": "Connect using socket file",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
"gui_settings_control_port_label": "Control port",
"gui_settings_socket_file_label": "Socket file",
"gui_settings_authenticate_label": "Autentizační možnosti Toru",
"gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace",
"gui_settings_authenticate_password_option": "Heslo",
- "gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Heslo",
- "gui_settings_cookie_label": "Cesta ke cookie",
- "gui_settings_button_test": "Test Settings",
"gui_settings_button_save": "Uložit",
"gui_settings_button_cancel": "Zrušit",
"settings_saved": "Nastavení uloženo do {}",
@@ -78,5 +59,24 @@
"settings_error_missing_password": "Připojen k ovladači Toru, ale vyžaduje heslo pro autentizaci.",
"settings_error_unreadable_cookie_file": "Připojen k ovladači Toru, ale nejde se autentizovat, protože heslo je možná špatné a váš uživatel nemá povolení číst soubor cookie.",
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
- "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work."
+ "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
+ "give_this_url_receive": "Sděl tuto adresu odesilateli:",
+ "give_this_url_receive_stealth": "Sdělte tuto adresu a HidServAuth odesilateli:",
+ "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt",
+ "not_a_readable_file": "{0:s} soubor není čitelný.",
+ "timeout_download_still_running": "Čeká se na konec stahování",
+ "systray_menu_exit": "Ukončit",
+ "systray_download_started_title": "Stahování pomocí OnionShare začalo",
+ "systray_download_started_message": "Někdo stahuje vaše soubory",
+ "systray_download_completed_title": "Stahování pomocí OnionShare skončilo",
+ "systray_download_completed_message": "Uživatel dokončil stahování vašich souborů",
+ "systray_download_canceled_title": "Stahování pomocí OnionShare bylo zrušeno",
+ "systray_download_canceled_message": "Uživatel přerušil stahování souboru",
+ "systray_upload_started_title": "Začalo nahrávání pomocí OnionShare",
+ "systray_upload_started_message": "Někdo právě začal nahrávat soubory na váš počítač",
+ "gui_share_stop_server_shutdown_timeout": "Zastavit sdílení ({}s zbývá)",
+ "gui_receive_start_server": "Spustit mód přijímání",
+ "gui_receive_stop_server": "Zastavit přijímání",
+ "gui_receive_stop_server_shutdown_timeout": "Zastavit mód přijímání ({}s zbývá)",
+ "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token"
}
diff --git a/share/locale/da.json b/share/locale/da.json
index 2c8d0c78..39edcf22 100644
--- a/share/locale/da.json
+++ b/share/locale/da.json
@@ -1,136 +1,220 @@
{
- "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.",
- "preparing_files": "Forbereder filer som skal deles.",
- "wait_for_hs": "Venter på at HS bliver klar:",
- "wait_for_hs_trying": "Prøver...",
- "wait_for_hs_nope": "Endnu ikke klar.",
- "wait_for_hs_yup": "Klar!",
- "give_this_url": "Giv denne URL til personen du sender filen til:",
- "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:",
- "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren",
+ "config_onion_service": "Opsætter onion-tjeneste på port {0:d}.",
+ "preparing_files": "Komprimerer filer.",
+ "give_this_url": "Giv adressen til modtageren:",
+ "give_this_url_stealth": "Giv adressen og HidServAuth-linjen til modtageren:",
+ "ctrlc_to_stop": "Tryk på Ctrl+C for at stoppe serveren",
"not_a_file": "{0:s} er ikke en gyldig fil.",
"not_a_readable_file": "{0:s} er ikke en læsbar fil.",
- "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.",
- "download_page_loaded": "Downloadside indlæst",
- "other_page_loaded": "URL indlæst",
- "close_on_timeout": "Lukker automatisk da timeout er nået",
- "closing_automatically": "Lukker automatisk da download er færdig",
- "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop",
- "large_filesize": "Advarsel: Det kan tage timer at sende store filer",
- "error_tails_invalid_port": "Ugyldig værdi, port skal være et heltal",
- "error_tails_unknown_root": "Ukendt fejl med Tails-rodproces",
+ "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten",
+ "other_page_loaded": "Adresse indlæst",
+ "close_on_timeout": "Stoppede fordi timer med autostop løb ud",
+ "closing_automatically": "Stoppede fordi overførslen er færdig",
+ "timeout_download_still_running": "Venter på at download skal blive færdig",
+ "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling",
"systray_menu_exit": "Afslut",
- "systray_download_started_title": "OnionShare-download startet",
- "systray_download_started_message": "En bruger startede download af dine filer",
+ "systray_download_started_title": "OnionShare-download begyndte",
+ "systray_download_started_message": "En bruger begyndte download af dine filer",
"systray_download_completed_title": "OnionShare-download færdig",
"systray_download_completed_message": "Brugeren er færdig med at downloade dine filer",
"systray_download_canceled_title": "OnionShare-download annulleret",
"systray_download_canceled_message": "Brugeren annullerede downloaden",
- "help_local_only": "Undlad at bruge tor: kun til udvikling",
- "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig",
- "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder",
- "help_transparent_torification": "Mit system er gennemsigtigt torifiseret",
- "help_stealth": "Opret usynlig onion-tjeneste (avanceret)",
- "help_debug": "Log programfejl til stdout, og log webfejl til disk",
+ "help_local_only": "Brug ikke Tor (kun til udvikling)",
+ "help_stay_open": "Fortsæt deling efter filerne er blevet sendt",
+ "help_shutdown_timeout": "Stop deling efter et vist antal sekunder",
+ "help_stealth": "Brug klientautentifikation (avanceret)",
+ "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk",
"help_filename": "Liste over filer eller mapper som skal deles",
- "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)",
- "gui_drag_and_drop": "Træk og slip\nfiler her",
+ "help_config": "Tilpasset placering af JSON-konfigurationsfil (valgfri)",
+ "gui_drag_and_drop": "Træk og slip filer og mapper her\nfor at starte deling",
"gui_add": "Tilføj",
"gui_delete": "Slet",
"gui_choose_items": "Vælg",
- "gui_start_server": "Start deling",
- "gui_stop_server": "Stop deling",
- "gui_copy_url": "Kopiér URL",
+ "gui_share_start_server": "Begynd at dele",
+ "gui_share_stop_server": "Stop deling",
+ "gui_copy_url": "Kopiér adresse",
"gui_copy_hidservauth": "Kopiér HidServAuth",
- "gui_downloads": "Downloads:",
+ "gui_downloads": "Downloadhistorik",
"gui_canceled": "Annulleret",
- "gui_copied_url": "Kopierede URL til udklipsholder",
- "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder",
- "gui_starting_server1": "Starter Tor onion-tjeneste...",
- "gui_starting_server2": "Databehandler filer...",
- "gui_please_wait": "Vent venligst...",
- "error_hs_dir_cannot_create": "Kan ikke oprette onion-tjenestens mappe {0:s}",
- "error_hs_dir_not_writable": "onion-tjenestens mappe {0:s} er skrivebeskyttet",
- "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse",
- "gui_download_progress_complete": "%p%, tid forløbet: {0:s}",
- "gui_download_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)",
- "gui_download_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%",
- "version_string": "Onionshare {0:s} | https://onionshare.org/",
- "gui_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.",
+ "gui_copied_url": "OnionShare-adressen blev kopieret til udklipsholderen",
+ "gui_copied_hidservauth": "HidServAuth-linjen blev kopieret til udklipsholderen",
+ "gui_please_wait": "Starter... klik for at annullere.",
+ "gui_download_upload_progress_complete": ".",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?",
"gui_quit_warning_quit": "Afslut",
- "gui_quit_warning_dont_quit": "Afslut ikke",
- "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.",
- "zip_progress_bar_format": "Databehandler filer: %p%",
- "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.",
- "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.",
+ "gui_quit_warning_dont_quit": "Annuller",
+ "error_rate_limit": "Nogen har foretaget for mange forkerte forsøg på din adresse, hvilket kan betyde at de prøver at gætte det, så OnionShare har stoppet serveren. Start deling igen og send en ny adresse til modtageren for at dele.",
+ "zip_progress_bar_format": "Komprimerer: %p%",
+ "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.",
"gui_settings_window_title": "Indstillinger",
- "gui_settings_stealth_label": "Usynlig (avanceret)",
- "gui_settings_stealth_option": "Opret usynlige onion-tjenester",
- "gui_settings_stealth_option_details": "Det gør OnionShare mere sikker, men også mere besværlig for modtageren at oprette forbindelse til den.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">Mere information</a>.",
- "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.",
- "gui_settings_autoupdate_label": "Søg efter opdateringer",
- "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer",
+ "gui_settings_stealth_option": "Brug klientautentifikation",
+ "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu kan klikke for at kopiere din HidServAuth.",
+ "gui_settings_autoupdate_label": "Søg efter ny version",
+ "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version",
"gui_settings_autoupdate_timestamp": "Sidste søgning: {}",
"gui_settings_autoupdate_timestamp_never": "Aldrig",
- "gui_settings_autoupdate_check_button": "Søg efter opdateringer",
- "gui_settings_sharing_label": "Valgmuligheder for deling",
- "gui_settings_close_after_first_download_option": "Stop deling efter første download",
- "gui_settings_systray_notifications": "Vis skrivebordsnotifikationer",
+ "gui_settings_autoupdate_check_button": "Søg efter ny version",
+ "gui_settings_sharing_label": "Delingsindstillinger",
+ "gui_settings_close_after_first_download_option": "Stop deling efter filerne er blevet sendt",
"gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?",
- "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare",
- "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser",
+ "gui_settings_connection_type_bundled_option": "Brug Tor-versionen som er indbygget i OnionShare",
+ "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser",
"gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport",
"gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil",
- "gui_settings_connection_type_test_button": "Test Tor-indstillinger",
+ "gui_settings_connection_type_test_button": "Test forbindelsen til Tor",
"gui_settings_control_port_label": "Kontrolport",
"gui_settings_socket_file_label": "Sokkelfil",
"gui_settings_socks_label": "SOCKS-port",
- "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation",
+ "gui_settings_authenticate_label": "Indstillinger for Tor-autentifikation",
"gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation",
"gui_settings_authenticate_password_option": "Adgangskode",
- "gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Adgangskode",
- "gui_settings_cookie_label": "Cookiesti",
"gui_settings_tor_bridges": "Understøttelse af Tor-bro",
"gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer",
- "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter",
- "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbygget obfs4 udskiftelige transporter (kræver obfs4proxy)",
- "gui_settings_tor_bridges_custom_radio_option": "Brug brugerdefinerede broer",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbyggede obfs4 udskiftelige transporter",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbyggede obfs4 udskiftelige transporter (kræver obfs4proxy)",
+ "gui_settings_tor_bridges_custom_radio_option": "Brug tilpassede broer",
"gui_settings_tor_bridges_custom_label": "Du kan få broer fra <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
- "gui_settings_tor_bridges_invalid": "Ingen af broerne du leverede ser ud til at være gyldige, så de ignoreres.\nPrøv venligst igen med gyldige broer.",
+ "gui_settings_tor_bridges_invalid": "Ingen at de broer du tilføjede virker.\nDobbeltklik på dem eller tilføj andre.",
"gui_settings_button_save": "Gem",
"gui_settings_button_cancel": "Annuller",
"gui_settings_button_help": "Hjælp",
- "gui_settings_shutdown_timeout_choice": "Sæt timer til automatisk stop?",
"gui_settings_shutdown_timeout": "Stop delingen ved:",
"settings_saved": "Indstillinger gemt til {}",
- "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.",
- "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.",
- "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontroller på {}:{}",
- "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontroller med sokkelfilen {}",
- "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det en Tor-kontroller?",
- "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere",
- "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.",
- "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.",
- "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.",
- "settings_error_bundled_tor_canceled": "Tor-processen lukkede inden den blev færdig med at oprette forbindelse.",
- "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}",
- "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}",
- "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.",
- "connecting_to_tor": "Forbundet til Tor-netværket",
- "update_available": "Der findes en OnionShare-opdatering. <a href='{}'>Klik her</a> for at downloade den.<br><br>Installeret version: {}<br>Seneste version: {}",
- "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.",
- "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.",
- "update_not_available": "Du kører den seneste version af OnionShare.",
- "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?",
- "gui_tor_connection_ask_open_settings": "Åbn indstillinger",
+ "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da dine indstillingerne ikke giver mening.",
+ "settings_error_automatic": "Kunne ikke oprette forbindelse til Tor-kontrolleren. Kører Tor Browser (tilgængelige fra torproject.org) i baggrunden?",
+ "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontrolleren på {}:{}.",
+ "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontrolleren med sokkelfilen {}.",
+ "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?",
+ "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.",
+ "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontrolleren, men adgangskoden kan være forkert, eller din bruger har ikke tilladelse til at læse cookiefilen.",
+ "settings_error_bundled_tor_not_supported": "Brug af Tor-versionen som kom med OnionShare virker ikke i udviklertilstand på Windows eller macOS.",
+ "settings_error_bundled_tor_timeout": "For længe om at oprette forbindelse til Tor. Måske har du ikke forbindelse til internettet, eller går dit systems ur forkert?",
+ "settings_error_bundled_tor_broken": "OnionShare kunne ikke oprette forbindelse til Tor i baggrunden:\n{}",
+ "settings_test_success": "Forbundet til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}.\nUnderstøtter klientautentifikation: {}.\nUnderstøtter næste generations .onion-adresser: {}.",
+ "error_tor_protocol_error": "Der opstod en fejl med Tor: {}",
+ "connecting_to_tor": "Opretter forbindelse til Tor-netværket",
+ "update_available": "Der findes en ny OnionShare. <a href='{}'>Klik her</a> for at hente den.<br><br>Du bruger {} og den seneste er {}.",
+ "update_error_check_error": "Kunne ikke søge efter nye versioner: OnionShare-webstedet siger at den seneste version er den ugenkendte '{}'…",
+ "update_error_invalid_latest_version": "Kunne ikke søge efter ny version: Måske har du ikke forbindelse til Tor, eller er OnionShare-webstedet nede?",
+ "update_not_available": "Du kører den seneste OnionShare.",
+ "gui_tor_connection_ask": "Åbn indstillingerne for at rette forbindelsen til Tor?",
+ "gui_tor_connection_ask_open_settings": "Ja",
"gui_tor_connection_ask_quit": "Afslut",
- "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket i Indstillinger.",
- "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.",
- "gui_tor_connection_lost": "Afbryder forbindelsen fra Tor.",
- "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.",
- "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.",
+ "gui_tor_connection_error_settings": "Prøv at ændre måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillingerne.",
+ "gui_tor_connection_canceled": "Kunne ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at opsætte dens forbindelse til Tor.",
+ "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.",
+ "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.",
+ "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.",
"share_via_onionshare": "Del via OnionShare",
- "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)",
- "persistent_url_in_use": "Denne deling bruger en vedvarende URL"
+ "gui_save_private_key_checkbox": "Brug en vedvarende adresse",
+ "gui_copied_url_title": "Kopierede OnionShare-adresse",
+ "gui_copied_hidservauth_title": "Kopierede HidServAuth",
+ "gui_quit_title": "Klap lige hesten",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Brug indbyggede meek_lite (Azure) udskiftelige transporter",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Brug indbyggede meek_lite (Azure) udskiftelige transporter (kræver obfs4proxy)",
+ "gui_settings_shutdown_timeout_checkbox": "Brug timer med autostop",
+ "gui_url_label_persistent": "Delingen stopper ikke automatisk.<br><br>Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).",
+ "gui_url_label_stay_open": "Delingen stopper ikke automatisk.",
+ "gui_url_label_onetime": "Delingen stopper efter den første download.",
+ "gui_url_label_onetime_and_persistent": "Delingen stopper ikke automatisk.<br><br>Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).",
+ "gui_file_info": "{} filer, {}",
+ "gui_file_info_single": "{} fil, {}",
+ "info_in_progress_downloads_tooltip": "{} igangværende downloads",
+ "info_completed_downloads_tooltip": "{} færdige downloads",
+ "give_this_url_receive": "Giv adressen til afsenderen:",
+ "give_this_url_receive_stealth": "Giv adressen og HidServAuth til afsenderen:",
+ "systray_upload_started_title": "OnionShare-upload begyndte",
+ "systray_upload_started_message": "En bruger begyndte at uploade filer til din computer",
+ "help_receive": "Modtager aktier i stedet for at sende dem",
+ "gui_share_stop_server_shutdown_timeout": "Stop deling ({}s tilbage)",
+ "gui_receive_quit_warning": "Du er i færd med at modtage filer. Er du sikker på du ønsker at stoppe med at OnionShare?",
+ "gui_settings_whats_this": "<a href='{0:s}'>Hvad er dette?</a>",
+ "gui_settings_general_label": "Generel opsætning",
+ "gui_upload_in_progress": "Upload begyndte {}",
+ "gui_download_in_progress": "Download begyndte {}",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}",
+ "gui_receive_start_server": "Start modtagetilstand",
+ "gui_receive_stop_server": "Stop modtagetilstand",
+ "gui_receive_stop_server_shutdown_timeout": "Stop modtagetilstand ({}s tilbage)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}",
+ "gui_no_downloads": "Ingen downloads endnu",
+ "error_tor_protocol_error_unknown": "Der opstod en ukendt fejl med Tor",
+ "error_invalid_private_key": "Den private nøgletype understøttes ikke",
+ "gui_use_legacy_v2_onions_checkbox": "Brug forældede adresser",
+ "gui_status_indicator_share_stopped": "Klar til at dele",
+ "gui_status_indicator_share_working": "Starter…",
+ "gui_status_indicator_share_started": "Deler",
+ "gui_status_indicator_receive_stopped": "Klar til at modtage",
+ "gui_status_indicator_receive_working": "Starter…",
+ "gui_status_indicator_receive_started": "Modtager",
+ "receive_mode_received_file": "Modtaget: {}",
+ "gui_mode_share_button": "Del filer",
+ "gui_mode_receive_button": "Modtag filer",
+ "gui_settings_receiving_label": "Modtagelsesindstillinger",
+ "gui_settings_downloads_label": "Gem filer til",
+ "gui_settings_downloads_button": "Gennemse",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Modtagetilstand kan ikke stoppes af afsenderen",
+ "gui_settings_public_mode_checkbox": "Offentlig tilstand",
+ "systray_close_server_title": "OnionShare-server lukket",
+ "systray_close_server_message": "En bruger lukkede serveren",
+ "systray_page_loaded_title": "Siden er indlæst",
+ "systray_download_page_loaded_message": "En bruger indlæste downloadsiden",
+ "systray_upload_page_loaded_message": "En bruger indlæste uploadsiden",
+ "gui_uploads": "Uploadhistorik",
+ "gui_no_uploads": "Ingen uploads endnu",
+ "gui_clear_history": "Ryd alle",
+ "gui_upload_finished_range": "Uploadede {} til {}",
+ "gui_upload_finished": "Uploadet {}",
+ "gui_settings_language_label": "Foretrukne sprog",
+ "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft.",
+ "gui_settings_meek_lite_expensive_warning": "Advarsel: meek_lite-broerne er meget dyre at køre for Tor-projektet.<br><br>Brug dem kun hvis du ikke er i stand til at oprette forbindelse til Tor direkte, via obfs4-transporter eller andre normale broer.",
+ "gui_share_url_description": "<b>Alle</b> med OnionShare-adressen kan <b>downloade</b> dine filer med <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Alle</b> med OnionShare-adressen kan <b>uploade</b> filer til din computer med <b>Tor Browser</b>: <img src='{}' />",
+ "history_in_progress_tooltip": "{} igangværende",
+ "history_completed_tooltip": "{} færdige",
+ "info_in_progress_uploads_tooltip": "{} igangværende upload(s)",
+ "info_completed_uploads_tooltip": "{} upload(s) færdige",
+ "error_cannot_create_downloads_dir": "Kunne ikke oprette modtagetilstand-mappe: {}",
+ "receive_mode_downloads_dir": "Filer som sendes til dig vises i denne mappe: {}",
+ "receive_mode_warning": "Advarsel: Modtagetilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.",
+ "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.<br><br><b>Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.</b>",
+ "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter",
+ "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}",
+ "timeout_upload_still_running": "Venter på at upload skal blive færdig",
+ "gui_add_files": "Tilføj filer",
+ "gui_add_folder": "Tilføj mappe",
+ "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste",
+ "error_cannot_create_data_dir": "Kunne ikke oprette OnionShare-datamappe: {}",
+ "receive_mode_data_dir": "Filer som sendes til dig vises i denne mappe: {}",
+ "gui_settings_data_dir_label": "Gem filer til",
+ "gui_settings_data_dir_browse_button": "Gennemse",
+ "systray_page_loaded_message": "OnionShare-adresse indlæst",
+ "systray_share_started_title": "Deling startet",
+ "systray_share_started_message": "Start på at sende filer til nogen",
+ "systray_share_completed_title": "Deling er færdig",
+ "systray_share_completed_message": "Færdig med at sende filer",
+ "systray_share_canceled_title": "Deling annulleret",
+ "systray_share_canceled_message": "Nogen annullerede modtagelsen af dine filer",
+ "systray_receive_started_title": "Modtagelse startede",
+ "systray_receive_started_message": "Nogen sender filer til dig",
+ "gui_all_modes_history": "Historik",
+ "gui_all_modes_clear_history": "Ryd alle",
+ "gui_all_modes_transfer_started": "Startede {}",
+ "gui_all_modes_transfer_finished_range": "Overførte {} - {}",
+ "gui_all_modes_transfer_finished": "Overførte {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} forløbet.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (udregner)",
+ "gui_all_modes_progress_eta": "{0:s}, anslået ankomsttidspunkt: {1:s}, %p%",
+ "gui_share_mode_no_files": "Der er endnu ikke sendt nogen filer",
+ "gui_share_mode_timeout_waiting": "Venter på at blive færdig med at sende",
+ "gui_receive_mode_no_files": "Der er endnu ikke modtaget nogen filer",
+ "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage",
+ "gui_all_modes_transfer_canceled_range": "Annullerede {} - {}",
+ "gui_all_modes_transfer_canceled": "Annullerede {}",
+ "gui_settings_onion_label": "Onion-indstillinger"
}
diff --git a/share/locale/de.json b/share/locale/de.json
index 7347e031..20ad9fe4 100644
--- a/share/locale/de.json
+++ b/share/locale/de.json
@@ -1,34 +1,201 @@
{
- "connecting_ctrlport": "Verbinde zum Tor-Kontrollport um den versteckten Dienst auf Port {0:d} laufen zu lassen.",
- "cant_connect_ctrlport": "Konnte keine Verbindung zum Tor-Kontrollport auf Port {0:s} aufbauen. Läuft Tor?",
- "cant_connect_socksport": "Konnte keine Verbindung zum Tor SOCKS5 Server auf Port {0:s} herstellen. OnionShare setzt voraus dass Tor Browser im Hintergrund läuft. Wenn du noch ihn noch noch nicht hast kannst du ihn unter https://www.torproject.org/ herunterladen.",
- "preparing_files": "Dateien werden vorbereitet.",
- "wait_for_hs": "Warte auf HS:",
- "wait_for_hs_trying": "Verbindungsversuch...",
- "wait_for_hs_nope": "Noch nicht bereit.",
- "wait_for_hs_yup": "Bereit!",
- "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:",
- "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten",
- "not_a_file": "{0:s} ist keine Datei.",
- "download_page_loaded": "Seite geladen",
+ "preparing_files": "Dateien werden komprimiert.",
+ "give_this_url": "Gib diese URL an den Empfänger:",
+ "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten",
+ "not_a_file": "{0:s} ist keine gültige Datei.",
"other_page_loaded": "URL geladen",
- "closing_automatically": "Halte automatisch an, da der Download beendet wurde",
- "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern",
- "error_tails_invalid_port": "Ungültiger Wert, Port muss eine ganze Zahl sein",
- "error_tails_unknown_root": "Unbekannter Fehler mit Tails root Prozess",
- "help_tails_port": "Nur für Tails: Port um den Firewall zu öffnen, starte onion service",
- "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung",
- "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde",
- "help_debug": "Fehler auf Festplatte schreiben",
- "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse",
- "gui_drag_and_drop": "Drag & drop\nDateien hier",
+ "closing_automatically": "Gestoppt, da der Download erfolgreich beendet wurde",
+ "large_filesize": "Warnung: Das Hochladen von großen Dateien kann Stunden dauern",
+ "help_local_only": "Tor nicht verwenden (nur für Entwicklung)",
+ "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde",
+ "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler auf die Festplatte",
+ "help_filename": "Liste der zu teilenden Dateien oder Ordner",
+ "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen",
"gui_add": "Hinzufügen",
"gui_delete": "Löschen",
"gui_choose_items": "Auswählen",
- "gui_start_server": "Server starten",
- "gui_stop_server": "Server anhalten",
+ "gui_share_start_server": "Server starten",
+ "gui_share_stop_server": "Server anhalten",
"gui_copy_url": "URL kopieren",
- "gui_downloads": "Downloads:",
+ "gui_downloads": "Bisherige Downloads",
"gui_copied_url": "URL wurde in die Zwischenablage kopiert",
- "gui_please_wait": "Bitte warten..."
+ "gui_please_wait": "Starte... Klicken zum Abbrechen.",
+ "timeout_download_still_running": "Warte auf Beendigung des Downloads",
+ "systray_menu_exit": "Beenden",
+ "gui_settings_authenticate_password_option": "Passwort",
+ "gui_settings_password_label": "Passwort",
+ "gui_settings_button_save": "Speichern",
+ "gui_settings_button_cancel": "Abbrechen",
+ "gui_settings_button_help": "Hilfe",
+ "gui_settings_shutdown_timeout": "Stoppe den Server bei:",
+ "systray_download_started_title": "OnionShare Download begonnen",
+ "systray_download_started_message": "Ein Nutzer hat begonnen, deine Dateien herunterzuladen",
+ "systray_download_completed_title": "OnionShare Download beendet",
+ "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen",
+ "systray_download_canceled_title": "OnionShare Download abgebrochen",
+ "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen",
+ "gui_copy_hidservauth": "HidServAuth kopieren",
+ "gui_canceled": "Abgebrochen",
+ "gui_copied_hidservauth_title": "HidServAuth kopiert",
+ "gui_quit_warning_quit": "Beenden",
+ "gui_quit_warning_dont_quit": "Abbrechen",
+ "gui_settings_window_title": "Eintellungen",
+ "gui_settings_autoupdate_timestamp": "Letzte Überprüfung: {}",
+ "gui_settings_autoupdate_timestamp_never": "Niemals",
+ "gui_settings_close_after_first_download_option": "Server nach dem ersten Download stoppen",
+ "gui_settings_connection_type_label": "Wie soll sich OnionShare mit Tor verbinden?",
+ "config_onion_service": "Richte den Onionservice auf Port {0:d} ein.",
+ "give_this_url_stealth": "Gib dem Empfänger diese URL und die HidServAuth-Zeile:",
+ "give_this_url_receive": "Gib diese URL dem Sender:",
+ "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:",
+ "not_a_readable_file": "{0:s} kann nicht gelesen werden.",
+ "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onionservice zu starten",
+ "close_on_timeout": "Angehalten da der auto-stop Timer abgelaufen ist",
+ "systray_upload_started_title": "OnionShare Upload wurde gestartet",
+ "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen",
+ "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)",
+ "help_receive": "Empfange Dateien anstatt sie zu senden",
+ "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab",
+ "gui_settings_connection_type_control_port_option": "Verbinde über den control port",
+ "gui_settings_connection_type_socket_file_option": "Verbinde über ein socket file",
+ "gui_settings_control_port_label": "Control port",
+ "gui_settings_socket_file_label": "Socket file",
+ "gui_settings_socks_label": "SOCKS Port",
+ "gui_settings_authenticate_no_auth_option": "Keine Authentifizierung, oder Authentifizierung per cookie",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Benutze keine bridges",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Benutze eingebaute obfs4 pluggable transports",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Benutze eingebaute obfs4 pluggable transports (benötigt obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Benutze eingebaute meek_lite (Amazon) pluggable transports",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Benutze eingebaute meek_lite (Azure) pluggable transports (benötigt obfs4proxy)",
+ "gui_settings_tor_bridges_custom_radio_option": "Benutze benutzerdefinierte bridges",
+ "gui_settings_tor_bridges_custom_label": "Bridges findest du unter <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_shutdown_timeout_checkbox": "Stoppe nach einer bestimmten Zeit",
+ "settings_error_auth": "Mit {}:{} verbinden aber nicht authentifiziert. Eventuell handelt es sich nicht um einen Tor controller?",
+ "settings_error_missing_password": "Mit dem Tor controller verbunden, aber er benötigt ein Passwort zur Authentifizierung.",
+ "connecting_to_tor": "Verbinde mit dem Tornetzwerk",
+ "gui_tor_connection_ask_quit": "Beenden",
+ "gui_tor_connection_lost": "Verbindung zu Tor getrennt.",
+ "help_stealth": "Nutze Klientauthorisierung (fortgeschritten)",
+ "gui_receive_start_server": "Starte den Empfangsmodus",
+ "gui_receive_stop_server": "Stoppe den Empfangsmodus",
+ "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {} Sekunden)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} ab",
+ "gui_no_downloads": "Bisher keine Downloads",
+ "gui_copied_url_title": "OnionShare-Adresse kopiert",
+ "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert",
+ "gui_download_upload_progress_complete": "%p%, {0:s} vergangen.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (berechne)",
+ "gui_download_upload_progress_eta": "{0:s}, Voraussichtliche Dauer: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Nicht so schnell",
+ "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?",
+ "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?",
+ "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu versenden.",
+ "zip_progress_bar_format": "Komprimierung: %p%",
+ "error_stealth_not_supported": "Um die Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.",
+ "gui_settings_whats_this": "<a href='{0:s}'>Was ist das?</a>",
+ "gui_settings_stealth_option": "Nutze Klientauthorisierung",
+ "gui_settings_autoupdate_label": "Suche nach einer neueren Version",
+ "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist",
+ "gui_settings_autoupdate_check_button": "Suche nach neuerer Version",
+ "gui_settings_general_label": "Allgemeine Einstellungen",
+ "gui_settings_sharing_label": "Freigabe-Einstellungen",
+ "gui_settings_connection_type_automatic_option": "Versuche Autokonfiguration mittels Tor Browser",
+ "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk",
+ "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor",
+ "gui_settings_tor_bridges": "Unterstützung für Tor Bridges",
+ "gui_settings_meek_lite_expensive_warning": "Achtung: Die meek_lite bridges sind für das Tor Projekt sehr kostspielig.<br><br> Nutze sie nur, wenn du dich nicht direkt, per obfs4 Transport oder über andere, normale bridges zum Tornetzwerk verbinden kannst.",
+ "gui_settings_tor_bridges_invalid": "Keine der bridges, die du angegeben hast, funktionieren.\nÜberprüfe sie oder gebe Andere an.",
+ "settings_error_unknown": "Kann nicht zum Tor controller verbinden, weil deine Einstellungen keinen Sinn ergeben.",
+ "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?",
+ "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.",
+ "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer darf die Cookiedatei nicht lesen.",
+ "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird, nicht nutzen.",
+ "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?",
+ "settings_error_bundled_tor_broken": "OnionShare konnte im Hintergrund nicht mit Tor verbinden:\n{}",
+ "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Client-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.",
+ "error_tor_protocol_error": "Es gab einen Fehler mit Tor: {}",
+ "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor",
+ "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt",
+ "update_available": "Es gibt eine neue Version von OnionShare. <a href='{}'>Klicke hier</a> um sie herunterzuladen.<br><br>Du benutzt {} und die neueste Version ist {}.",
+ "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShare-Seite sagt, die aktuelle Version ist nicht wiederzuerkennen '{}'…",
+ "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?",
+ "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.",
+ "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk in Ordnung zu bringen?",
+ "gui_tor_connection_ask_open_settings": "Ja",
+ "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.",
+ "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.",
+ "share_via_onionshare": "Teile es per OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat",
+ "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse",
+ "gui_share_url_description": "<b>Jeder</b> mit dieser OnionShareAdresse kann deine Dateien mit dem <b>Tor Browser</b> <b>herunterladen</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Jeder</b> mit dieser OnionShareAdresse kann mit dem <b>Tor Browser</b> Dateien auf deinen Computer <b>hochladen</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.<br><br>Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)",
+ "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.",
+ "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.",
+ "gui_status_indicator_share_working": "Starte…",
+ "gui_status_indicator_share_started": "Teilen",
+ "gui_status_indicator_receive_stopped": "Bereit zum Empfangen",
+ "gui_status_indicator_receive_working": "Starte…",
+ "gui_status_indicator_receive_started": "Empfange",
+ "gui_file_info": "{} Dateien, {}",
+ "gui_file_info_single": "{} Datei, {}",
+ "history_completed_tooltip": "{} abgeschlossen",
+ "info_in_progress_uploads_tooltip": "{} Upload(s) laufen",
+ "info_completed_uploads_tooltip": "{} Upload(s) vollständig",
+ "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}",
+ "receive_mode_downloads_dir": "Dateien, die dir geschickt werden, findest du in diesem Ordner: {}",
+ "receive_mode_warning": "Achtung: Im Empfängermodus können Leute Dateien auf deinen Computer laden. Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.",
+ "gui_receive_mode_warning": "Im Empfängermodus können Personen Dateien auf deinen Computer laden.<br><br><b>Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.</b>",
+ "receive_mode_received_file": "Empfangen: {}",
+ "gui_mode_share_button": "Versende Dateien",
+ "gui_mode_receive_button": "Empfange Dateien",
+ "gui_settings_receiving_label": "Empfangs-Einstellungen",
+ "gui_settings_downloads_label": "Speichere Dateien in",
+ "gui_settings_downloads_button": "Durchsuchen",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Der Empfängermodus kann vom Versender gestoppt werden",
+ "gui_settings_public_mode_checkbox": "Öffentlicher Modus",
+ "systray_close_server_title": "OnionShareServer gestoppt",
+ "systray_close_server_message": "Ein Nutzer hat den Server gestoppt",
+ "systray_download_page_loaded_message": "Ein Nutzer hat die Downloadseite geöffnet",
+ "systray_upload_page_loaded_message": "Ein Nutzer hat die Uploadseite geöffnet",
+ "gui_uploads": "Uploadhistorie",
+ "gui_no_uploads": "Bisher keine Uploads",
+ "gui_clear_history": "Alle löschen",
+ "gui_upload_in_progress": "Upload gestartet {}",
+ "gui_download_in_progress": "Download gestartet {}",
+ "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}",
+ "gui_settings_language_label": "Bevorzugte Sprache",
+ "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.",
+ "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)",
+ "timeout_upload_still_running": "Warte bis Upload vollständig ist",
+ "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.",
+ "gui_settings_connection_type_bundled_option": "Die integrierte Tor version von OnionShare nutzen",
+ "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.",
+ "gui_server_started_after_timeout": "Die Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.",
+ "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das Teilen zu starten.",
+ "gui_status_indicator_share_stopped": "Bereit zum teilen",
+ "history_in_progress_tooltip": "{} läuft",
+ "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt",
+ "systray_page_loaded_title": "Seite geladen",
+ "gui_upload_finished_range": "{} hochgeladen zu {}",
+ "gui_upload_finished": "{} hochgeladen",
+ "gui_add_files": "Dateien hinzufügen",
+ "gui_add_folder": "Ordner hinzufügen",
+ "gui_connect_to_tor_for_onion_settings": "Verbinde dich mit Tor, um die Einstellungen für onion services zu sehen",
+ "gui_url_label_onetime_and_persistent": "Dieser Server wird nicht automatisch stoppen. >br><br>Jeder nachfolgende Server wird die gleiche Adresse nutzen. (Um jedes mal eine andere Adresse zu nutzen, schalte \"Nutze eine gleichbleibende Adresse\" in den Einstellungen aus.)",
+ "gui_settings_onion_label": "Adresseinstellungen",
+ "error_cannot_create_data_dir": "Der Ordner für die OnionSharedateien konnte nicht erstellt werden: {}",
+ "receive_mode_data_dir": "Die Dateien, die dir geschickt wurden, findest du in folgendem Ordner: {}",
+ "gui_settings_data_dir_label": "Speichere Dateien in",
+ "gui_settings_data_dir_browse_button": "Durchsuchen",
+ "systray_page_loaded_message": "OnionShare-Adresse geladen",
+ "systray_share_started_title": "Freigabe gestartet",
+ "systray_share_started_message": "Upload von Dateien begonnen",
+ "systray_share_completed_title": "Freigabe erfolgt",
+ "systray_share_completed_message": "Dateien erfolgreich versandt",
+ "systray_share_canceled_title": "Freigabe abgebrochen",
+ "systray_share_canceled_message": "Jemand hat den Download deiner Dateien abgebrochen"
}
diff --git a/share/locale/el.json b/share/locale/el.json
new file mode 100644
index 00000000..4157c592
--- /dev/null
+++ b/share/locale/el.json
@@ -0,0 +1,217 @@
+{
+ "config_onion_service": "Δημιουργία onion service στην πύλη {0:d}.",
+ "preparing_files": "Συμπίεση αρχείων.",
+ "give_this_url": "Δώσε αυτή την διεύθυνση στον/στην παραλήπτη/τρια:",
+ "give_this_url_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και τη σειρά HidServAuth ως παραλήπτη:",
+ "give_this_url_receive": "Δώσε αυτή τη διεύθυνση στον/στην αποστολέα:",
+ "give_this_url_receive_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και το HidServAuth ως αποστολέα:",
+ "ctrlc_to_stop": "Πάτα Ctrl+C για να σταματήσεις το σέρβερ",
+ "not_a_file": "{0:s} δεν είναι έγκυρο αρχείο.",
+ "not_a_readable_file": "Το {0:s} δεν είναι αναγνώσιμο αρχείο.",
+ "no_available_port": "Δεν βρέθηκε διαθέσιμη θύρα για να ξεκινήσει η υπηρεσία onion",
+ "other_page_loaded": "Η διεύθυνση φορτώθηκε",
+ "close_on_timeout": "Τερματίστηκε γιατί το χρονόμετρο τερματισμού έφτασε στο τέλος",
+ "closing_automatically": "Τερματίστηκε επειδή η λήψη ολοκληρώθηκε",
+ "timeout_download_still_running": "Αναμονή ολοκλήρωσης της λήψης",
+ "large_filesize": "Προειδοποίηση: Η αποστολή μεγάλου όγκου δεδομένων μπορεί να διαρκέσει ώρες",
+ "systray_menu_exit": "Έξοδος",
+ "systray_download_started_title": "Η λήψη του OnionShare ξεκίνησε",
+ "systray_download_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να κατεβάζει τα αρχεία σου",
+ "systray_download_completed_title": "Η λήψη του OnionShare ολοκληρώθηκε",
+ "systray_download_completed_message": "Ο/η χρήστης/τρια ολοκλήρωσε την λήψη των αρχείων σου",
+ "systray_download_canceled_title": "Η λήψη του OnionShare ακυρώθηκε",
+ "systray_download_canceled_message": "Ο/η χρήστης/τρια ακύρωσε τη λήψη",
+ "systray_upload_started_title": "Η λήψη του OnionShare ξεκίνησε",
+ "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου",
+ "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)",
+ "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά την αποστολή των αρχείων",
+ "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων",
+ "help_stealth": "Κάντε χρήση εξουσιοδότησης πελάτη (Για προχωρημένους)",
+ "help_receive": "Λάβετε διαμοιρασμένα αρχεία αντι να τα στέλνετε",
+ "help_debug": "Κατέγραψε τα σφάλματα του OnionShare στο stdout (συνήθως οθόνη) και τα σφάλματα web στον δίσκο",
+ "help_filename": "Λίστα αρχείων ή φακέλων για μοίρασμα",
+ "help_config": "Ορίστε σημείο αποθήκευσης αρχείου JSON",
+ "gui_drag_and_drop": "Σύρτε και αφήστε αρχεία και φακέλους\nγια να αρχίσετε να τα μοιράζεστε",
+ "gui_add": "Προσθήκη",
+ "gui_delete": "Διαγραφή",
+ "gui_choose_items": "Επιλογή",
+ "gui_share_start_server": "Εκκίνηση μοιράσματος",
+ "gui_share_stop_server": "Τερματισμός μοιράσματος",
+ "gui_share_stop_server_shutdown_timeout": "Τερματισμός μοιράσματος (απομένουν {}\")",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}",
+ "gui_receive_start_server": "Εκκίνηση κατάστασης λήψης",
+ "gui_receive_stop_server": "Τερματισμός κατάστασης λήψης",
+ "gui_receive_stop_server_shutdown_timeout": "Τερματισμός κατάστασης λήψης (υπολοίπονται {}\")",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}",
+ "gui_copy_url": "Αντιγραφή διεύθυνσης",
+ "gui_copy_hidservauth": "Αντιγραφή HidServAuth",
+ "gui_downloads": "Ιστορικό Λήψεων",
+ "gui_no_downloads": "Καμία λήψη ως τώρα",
+ "gui_canceled": "Ακυρώθηκε",
+ "gui_copied_url_title": "Αντεγραμμένη διεύθυνση OnionShare",
+ "gui_copied_url": "Αντεγράφη η διεύθυνση OnionShare στον πίνακα",
+ "gui_copied_hidservauth_title": "Αντιγραμμένος HidServAuth",
+ "gui_copied_hidservauth": "Η σειρά HidServAuth αντεγράφη στον πίνακα",
+ "gui_please_wait": "Ξεκινάμε... Κάντε κλικ για ακύρωση.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} πέρασαν.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (υπολογισμός)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Όχι τόσο γρήγορα",
+ "gui_share_quit_warning": "Είστε στη διαδικασία αποστολής αρχείων. Είστε σίγουρος πως θέλετε να ακυρώσετε το OnionShare?",
+ "gui_receive_quit_warning": "Είστε στη διαδικασία παραλαβής αρχείων. Είστε σίγουρος πώς θέλετε να ακυρώσετε το OnionShare?",
+ "gui_quit_warning_quit": "Έξοδος",
+ "gui_quit_warning_dont_quit": "Ακύρωση",
+ "error_rate_limit": "Κάποιος προσπάθησε επανειλημμένα να μπει στη διεύθυνσή σας, το οποίο σημαίνει πως μπορεί να προσπαθεί να την μαντέψει, οπότε το OnionShare σταμάτησε τον server. Ξεκινήστε πάλι το μοίρασμα και στείλτε στον παραλήπτη μία νέα διεύθυνση για κοινοποίηση.",
+ "zip_progress_bar_format": "Συμπίεση: %p%",
+ "error_stealth_not_supported": "Για τη χρήση άδειας χρήστη, χρειάζεστε τουλάχιστον το Tor 0.2.9.1-alpha (ή τον Tor Browser 6.5) και το python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.",
+ "gui_settings_window_title": "Ρυθμίσεις",
+ "gui_settings_whats_this": "<a href='{0:s}'> Τί είναι αυτό? </a>",
+ "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη",
+ "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα να επιλέξετε την αντιγραφή του HidServAuth σας.",
+ "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση",
+ "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση",
+ "gui_settings_autoupdate_timestamp": "Τελευταίος έλεγχος: {}",
+ "gui_settings_autoupdate_timestamp_never": "Ποτέ",
+ "gui_settings_autoupdate_check_button": "Έλεγχος για νέα έκδοση",
+ "gui_settings_general_label": "Γενικές ρυθμίσεις",
+ "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης",
+ "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης αρχείων μετά την αποστολή τους",
+ "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?",
+ "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare",
+ "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser",
+ "gui_settings_connection_type_control_port_option": "Σύνδεση μέσω πύλης ελέγχου",
+ "gui_settings_connection_type_socket_file_option": "Σύνδεση μέσω αρχείου μετάβασης",
+ "gui_settings_connection_type_test_button": "Έλεγχος της σύνδεσης με το Tor",
+ "gui_settings_control_port_label": "Πύλη ελέγχου",
+ "gui_settings_socket_file_label": "Αρχείο μετάβασης",
+ "gui_settings_socks_label": "πύλη SOCKS",
+ "gui_settings_authenticate_label": "Ρυθμίσεις επαλήθευσης Tor",
+ "gui_settings_authenticate_no_auth_option": "Καμία επαλήθευση ή επαλήθευση cookie",
+ "gui_settings_authenticate_password_option": "Κωδικός",
+ "gui_settings_password_label": "Κωδικός",
+ "gui_settings_tor_bridges": "Στήριξη Tor bridge",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Μην χρησιμοποιείτε bridges",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Χρησιμοποιήστε ενσωματωμένα obfs4 pluggable transports",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Χρησμοποιήστε ενσωματωμένα obfs4 pluggable transports (απαιτείται obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports (απαιτεί obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Προσοχή: Τα meek_lite bridges επιβαρύνουν πολύ το Tor Project στη λειτουργία. <br><br> Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλα κανονικά bridges.",
+ "gui_settings_tor_bridges_custom_radio_option": "Χρήση κανονικών bridges",
+ "gui_settings_tor_bridges_custom_label": "Αποκτήστε bridges στο <a href=\"https://bridges.torproject.org/options\"> https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Δεν λειτούργησε κάποιο από τα bridges που προσθέσατε.\nΞαναελέγξτε τα ή επιλέξτε άλλα.",
+ "gui_settings_button_save": "Αποθήκευση",
+ "gui_settings_button_cancel": "Ακύρωση",
+ "gui_settings_button_help": "Βοήθεια",
+ "gui_settings_shutdown_timeout_checkbox": "Χρήση χρονομέτρου αυτόματου τερματισμού",
+ "gui_settings_shutdown_timeout": "Τερματισμός της κοινοποίησης στα:",
+ "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.",
+ "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?",
+ "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.",
+ "settings_error_socket_file": "Ανέφικτη η σύνδεση με τον ελεγκτή Tor, κάνοντας χρήση αρχείου socket {}.",
+ "settings_error_auth": "Εγινε σύνδεση με {}:{}, αλλα δεν μπορεί να γίνει πιστοποίηση. Ισως δεν ειναι ενας ελεγκτής Tor?",
+ "settings_error_missing_password": "Εγινε σύνδεση με ελεγκτή Tor, αλλά απαιτείται κωδικός για πιστοποίηση.",
+ "settings_error_unreadable_cookie_file": "Εγινε σύνδεση με ελεγκτή Tor, αλλα ο κωδικός πιθανόν να είναι λάθος ή ο χρήστης δεν επιτρέπεται να διαβάζει αρχεία cookie.",
+ "settings_error_bundled_tor_not_supported": "Η έκδοση Tor που συνοδεύει το OnionShare δεν λειτουργεί σε περιβάλλον προγραμματιστή σε Windows ή macOS.",
+ "settings_error_bundled_tor_timeout": "Η σύνδεση με Tor αργεί αρκετά. Ισως δεν είστε συνδεδεμένοι στο Διαδίκτυο ή το ρολόι σας δεν ειναι συγχρονισμένο?",
+ "settings_error_bundled_tor_broken": "Το OnionShare δεν μπορεί να συνδεθεί με το Tor στο παρασκήνιο:\n{}",
+ "settings_test_success": "Εγινε σύνδεση με τον ελεγκτή Tor.\n\nΕκδοση Tor: {}\nΥποστηρίζει εφήμερες υπηρεσίες onion: {}.\nΥποστηρίζει πιστοποίηση πελάτη: {}.\nΥποστηρίζει νέας γενιάς διευθύνσεις .onion: {}.",
+ "error_tor_protocol_error": "Υπήρξε σφάλμα με το Tor: {}",
+ "error_tor_protocol_error_unknown": "Υπήρξε άγνωστο σφάλμα με το Tor",
+ "error_invalid_private_key": "Αυτο το ιδιωτικό κλειδί δεν υποστηρίζεται",
+ "connecting_to_tor": "Γίνεται σύνδεση με το δίκτυο Tor",
+ "update_available": "Βγήκε ενα νέο OnionShare. <a href='{}'>Κάντε κλικ εδώ</a> για να το λάβετε.<br><br>Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.",
+ "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση δεν αναγνωρίζεται '{}'…",
+ "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένοι στο Tor ή ο ιστότοπος OnionShare είναι κάτω?",
+ "update_not_available": "Εχετε την πιό πρόσφατη έκδοση OnionShare.",
+ "gui_tor_connection_ask": "Να ανοίξετε τις ρυθμίσεις για να επιλύσετε την σύνδεση με το Tor?",
+ "gui_tor_connection_ask_open_settings": "Ναι",
+ "gui_tor_connection_ask_quit": "Εξοδος",
+ "gui_tor_connection_error_settings": "Προσπαθήστε να αλλάξετε τον τρόπο σύνδεσης του OnionShare, με το δίκτυο Tor, από τις ρυθμίσεις.",
+ "gui_tor_connection_canceled": "Δεν μπόρεσε να γίνει σύνδεση με Tor.\n\nΕλέγξτε ότι είστε συνδεδεμένοι στο Διαδίκτυο, επανεκινήστε το OnionShare και ρυθμίστε την σύνδεση με το Tor.",
+ "gui_tor_connection_lost": "Εγινε αποσύνδεση απο το Tor.",
+ "gui_server_started_after_timeout": "Η λειτουργία auto-stop τερματίστηκε πριν την εκκίνηση διακομιστή.\nΠαρακαλώ κάντε εναν νέο διαμοιρασμό.",
+ "gui_server_timeout_expired": "Η λειτουργία auto-stop ήδη τερματίστηκε.\nΕνημερώστε την για να ξεκινήσετε τον διαμοιρασμό.",
+ "share_via_onionshare": "Κάντε το OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Χρηση \"παραδοσιακών\" διευθύνσεων",
+ "gui_save_private_key_checkbox": "Χρήση μόνιμης διεύθυνσης",
+ "gui_share_url_description": "<b>Οποιοσδήποτε</b> με αυτήν την διεύθυνση OnionShare, μπορεί να <b>κατεβάσει</b> τα αρχεία σας με χρήση <b> Φυλλομετρητη Tor</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Οποιοσδήποτε</b> με αυτήν την διεύθυνση OnionShare, μπορεί να <b>ανεβάσει</b> αρχεία στον υπολογιστή σας με χρήση του <b>Φυλλομετρητή Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.<br><br>Οποιοσδήποτε μετέπειτα διαμοιρασμός κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
+ "gui_url_label_stay_open": "Αυτος ο διαμοιρασμός δεν έχει auto-stop.",
+ "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει με την πρώτη λήψη.",
+ "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.<br><br>Οποιοσδήποτε μετέπειτα διαμοιρασμός θα κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
+ "gui_status_indicator_share_stopped": "Ετοιμο για διαμοιρασμό",
+ "gui_status_indicator_share_working": "Ξεκινάει…",
+ "gui_status_indicator_share_started": "Διαμοιράζει",
+ "gui_status_indicator_receive_stopped": "Ετοιμο για λήψη",
+ "gui_status_indicator_receive_working": "Ξεκινάει…",
+ "gui_status_indicator_receive_started": "Γίνεται λήψη",
+ "gui_file_info": "{} αρχεία, {}",
+ "gui_file_info_single": "{} αρχείο, {}",
+ "history_in_progress_tooltip": "{} σε εξέλιξη",
+ "history_completed_tooltip": "{} ολοκληρώθηκε",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "Προσοχή: η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας. Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.",
+ "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας.<br><br><b> Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.</b>",
+ "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει",
+ "receive_mode_received_file": "Ελήφθη: {}",
+ "gui_mode_share_button": "Διαμοίρασε αρχεία",
+ "gui_mode_receive_button": "Λήψη αρχείων",
+ "gui_settings_receiving_label": "Ρυθμίσεις λήψης",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "Δημόσια λειτουργία",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "Η σελίδα φορτώθηκε",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "Δεν μπορεί να ανοιχτεί ο φάκελος γιατί το nautilus δεν είναι διαθέσιμο. Το αρχείο είναι εδω: {}",
+ "gui_settings_language_label": "Προτιμώμενη γλώσσα",
+ "gui_settings_language_changed_notice": "Επανεκινήστε το OnionShare για να ενεργοποιηθεί η αλλαγή γλώσσας.",
+ "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος",
+ "gui_add_files": "Προσθέστε αρχεία",
+ "gui_add_folder": "Προσθέστε φάκελο",
+ "gui_connect_to_tor_for_onion_settings": "Συνδεθείτε με Tor για να δείτε τις ρυθμίσεις της υπηρεσίας onion",
+ "error_cannot_create_data_dir": "Δεν ήταν δυνατό να δημιουργηθεί φάκελος δεδομένων OnionShare: {}",
+ "receive_mode_data_dir": "Τα αρχεία που στάλθηκαν σε εσας εμφανίζοντε στον φάκελο: {}",
+ "gui_settings_data_dir_label": "Αποθήκευσε αρχεία στο",
+ "gui_settings_data_dir_browse_button": "Περιήγηση",
+ "systray_page_loaded_message": "Η διεύθυνση OnionShare φορτώθηκε",
+ "systray_share_started_title": "Ο διαμοιρασμός ξεκίνησε",
+ "systray_share_started_message": "Ξεκίνησε η αποστολή αρχείων σε κάποιον",
+ "systray_share_completed_title": "Ο διαμοιρασμός ολοκληρώθηκε",
+ "systray_share_completed_message": "Ολοκληρώθηκε η αποστολή αρχείων",
+ "systray_share_canceled_title": "Ο διαμοιρασμός ακυρώθηκε",
+ "systray_share_canceled_message": "Κάποιος ακύρωσε την λήψη των αρχείων σας",
+ "systray_receive_started_title": "Η λήψη ξεκίνησε",
+ "systray_receive_started_message": "Κάποιος σας στέλνει αρχεία",
+ "gui_all_modes_history": "Ιστορικό",
+ "gui_all_modes_clear_history": "Καθαρισμός όλων",
+ "gui_all_modes_transfer_started": "Ξεκινησε {}",
+ "gui_all_modes_transfer_finished_range": "Μεταφέρθηκαν {} - {}",
+ "gui_all_modes_transfer_finished": "Μεταφέρθηκαν {} - {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} διάρκεια.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (γίνεται υπολογισμός)",
+ "gui_all_modes_progress_eta": "{0:s}, εκτίμηση: {1:s}, %p%",
+ "gui_share_mode_no_files": "Δεν Στάλθηκαν Αρχεία Ακόμα",
+ "gui_share_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση αποστολής",
+ "gui_receive_mode_no_files": "Δεν Εγινε Καμμία Λήψη Αρχείων Ακόμα",
+ "gui_receive_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση της λήψης",
+ "gui_settings_onion_label": "Ρυθμίσεις Onion",
+ "gui_all_modes_transfer_canceled_range": "Ακυρώθηκε {} - {}",
+ "gui_all_modes_transfer_canceled": "Ακυρώθηκε {}"
+}
diff --git a/share/locale/en.json b/share/locale/en.json
index 09ead591..f7af9a80 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -1,156 +1,184 @@
{
- "config_onion_service": "Configuring onion service on port {0:d}.",
- "preparing_files": "Preparing files to share.",
- "wait_for_hs": "Waiting for HS to be ready:",
- "wait_for_hs_trying": "Trying...",
- "wait_for_hs_nope": "Not ready yet.",
- "wait_for_hs_yup": "Ready!",
- "give_this_url": "Give this address to the person you're sending the file to:",
- "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:",
- "ctrlc_to_stop": "Press Ctrl-C to stop server",
+ "config_onion_service": "Setting up onion service on port {0:d}.",
+ "preparing_files": "Compressing files.",
+ "give_this_url": "Give this address to the recipient:",
+ "give_this_url_stealth": "Give this address and HidServAuth line to the recipient:",
+ "give_this_url_receive": "Give this address to the sender:",
+ "give_this_url_receive_stealth": "Give this address and HidServAuth to the sender:",
+ "ctrlc_to_stop": "Press Ctrl+C to stop the server",
"not_a_file": "{0:s} is not a valid file.",
"not_a_readable_file": "{0:s} is not a readable file.",
- "no_available_port": "Could not start the Onion service as there was no available port.",
- "download_page_loaded": "Download page loaded",
+ "no_available_port": "Could not find an available port to start the onion service",
"other_page_loaded": "Address loaded",
- "close_on_timeout": "Stopped because timer expired",
- "closing_automatically": "Stopped because download finished",
- "timeout_download_still_running": "Waiting for download to complete",
- "large_filesize": "Warning: Sending large files could take hours",
- "error_tails_invalid_port": "Invalid value, port must be an integer",
- "error_tails_unknown_root": "Unknown error with Tails root process",
- "systray_menu_exit": "Quit",
- "systray_download_started_title": "OnionShare Download Started",
- "systray_download_started_message": "A user started downloading your files",
- "systray_download_completed_title": "OnionShare Download Finished",
- "systray_download_completed_message": "The user finished downloading your files",
- "systray_download_canceled_title": "OnionShare Download Canceled",
- "systray_download_canceled_message": "The user canceled the download",
- "help_local_only": "Do not attempt to use tor: for development only",
- "help_stay_open": "Keep onion service running after download has finished",
- "help_shutdown_timeout": "Shut down the onion service after N seconds",
- "help_transparent_torification": "My system is transparently torified",
- "help_stealth": "Create stealth onion service (advanced)",
- "help_debug": "Log application errors to stdout, and log web errors to disk",
+ "close_on_timeout": "Stopped because auto-stop timer ran out",
+ "closing_automatically": "Stopped because transfer is complete",
+ "large_filesize": "Warning: Sending a large share could take hours",
+ "help_local_only": "Don't use Tor (only for development)",
+ "help_stay_open": "Continue sharing after files have been sent",
+ "help_shutdown_timeout": "Stop sharing after a given amount of seconds",
+ "help_stealth": "Use client authorization (advanced)",
+ "help_receive": "Receive shares instead of sending them",
+ "help_debug": "Log OnionShare errors to stdout, and web errors to disk",
"help_filename": "List of files or folders to share",
- "help_config": "Path to a custom JSON config file (optional)",
+ "help_config": "Custom JSON config file location (optional)",
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
"gui_add": "Add",
+ "gui_add_files": "Add Files",
+ "gui_add_folder": "Add Folder",
"gui_delete": "Delete",
"gui_choose_items": "Choose",
- "gui_start_server": "Start Sharing",
- "gui_stop_server": "Stop Sharing",
- "gui_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
- "gui_stop_server_shutdown_timeout_tooltip": "Share will stop automatically at {}",
+ "gui_share_start_server": "Start sharing",
+ "gui_share_stop_server": "Stop sharing",
+ "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}",
+ "gui_receive_start_server": "Start Receive Mode",
+ "gui_receive_stop_server": "Stop Receive Mode",
+ "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}",
"gui_copy_url": "Copy Address",
"gui_copy_hidservauth": "Copy HidServAuth",
- "gui_downloads": "Downloads:",
"gui_canceled": "Canceled",
- "gui_copied_url_title": "Copied OnionShare address",
- "gui_copied_url": "The OnionShare address has been copied to clipboard",
+ "gui_copied_url_title": "Copied OnionShare Address",
+ "gui_copied_url": "OnionShare address copied to clipboard",
"gui_copied_hidservauth_title": "Copied HidServAuth",
- "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard",
- "gui_starting_server1": "Starting Tor onion service...",
- "gui_starting_server2": "Crunching files...",
- "gui_please_wait": "Starting... Click to cancel",
- "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}",
- "error_hs_dir_not_writable": "onion service dir {0:s} is not writable",
- "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
- "gui_download_progress_complete": "%p%, Time Elapsed: {0:s}",
- "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)",
- "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
- "version_string": "Onionshare {0:s} | https://onionshare.org/",
- "gui_quit_title": "Transfer in Progress",
- "gui_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
+ "gui_copied_hidservauth": "HidServAuth line copied to clipboard",
+ "gui_please_wait": "Starting… Click to cancel.",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Not so fast",
+ "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
+ "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?",
"gui_quit_warning_quit": "Quit",
"gui_quit_warning_dont_quit": "Cancel",
- "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.",
- "zip_progress_bar_format": "Crunching files: %p%",
- "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
- "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.",
+ "error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.",
+ "zip_progress_bar_format": "Compressing: %p%",
+ "error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.",
"gui_settings_window_title": "Settings",
- "gui_settings_stealth_label": "Stealth (advanced)",
- "gui_settings_stealth_option": "Create stealth onion services",
- "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
- "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
- "gui_settings_autoupdate_label": "Check for updates",
- "gui_settings_autoupdate_option": "Notify me when updates are available",
+ "gui_settings_whats_this": "<a href='{0:s}'>What's this?</a>",
+ "gui_settings_stealth_option": "Use client authorization",
+ "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now click to copy your HidServAuth.",
+ "gui_settings_autoupdate_label": "Check for new version",
+ "gui_settings_autoupdate_option": "Notify me when a new version is available",
"gui_settings_autoupdate_timestamp": "Last checked: {}",
"gui_settings_autoupdate_timestamp_never": "Never",
- "gui_settings_autoupdate_check_button": "Check For Updates",
- "gui_settings_sharing_label": "Sharing options",
- "gui_settings_close_after_first_download_option": "Stop sharing after first download",
- "gui_settings_systray_notifications": "Show desktop notifications",
+ "gui_settings_autoupdate_check_button": "Check for New Version",
+ "gui_settings_general_label": "General settings",
+ "gui_settings_onion_label": "Onion settings",
+ "gui_settings_sharing_label": "Sharing settings",
+ "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent",
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
- "gui_settings_connection_type_bundled_option": "Use Tor that is bundled with OnionShare",
- "gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser",
+ "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
+ "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser",
"gui_settings_connection_type_control_port_option": "Connect using control port",
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
- "gui_settings_connection_type_test_button": "Test Tor Settings",
+ "gui_settings_connection_type_test_button": "Test Connection to Tor",
"gui_settings_control_port_label": "Control port",
"gui_settings_socket_file_label": "Socket file",
"gui_settings_socks_label": "SOCKS port",
- "gui_settings_authenticate_label": "Tor authentication options",
+ "gui_settings_authenticate_label": "Tor authentication settings",
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
"gui_settings_authenticate_password_option": "Password",
- "gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Password",
- "gui_settings_cookie_label": "Cookie path",
- "gui_settings_tor_bridges": "Tor Bridge support",
+ "gui_settings_tor_bridges": "Tor bridge support",
"gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges",
"gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports",
"gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)",
- "gui_settings_tor_bridges_meek_lite_amazon_radio_option": "Use built-in meek_lite (Amazon) pluggable transports",
- "gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)",
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports",
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite bridges are very costly for the Tor Project to run.<br><br>Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.",
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges",
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
- "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid.\nPlease try again with valid bridges.",
+ "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.",
"gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel",
"gui_settings_button_help": "Help",
"gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer",
"gui_settings_shutdown_timeout": "Stop the share at:",
- "settings_saved": "Settings saved to {}",
- "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.",
- "settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.",
- "settings_error_socket_port": "Can't connect to Tor controller on {}:{}.",
- "settings_error_socket_file": "Can't connect to Tor controller using socket file {}.",
+ "settings_error_unknown": "Can't connect to Tor controller because your settings don't make sense.",
+ "settings_error_automatic": "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?",
+ "settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.",
+ "settings_error_socket_file": "Can't connect to the Tor controller using socket file {}.",
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
- "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.",
- "settings_error_bundled_tor_not_supported": "Bundled Tor is not supported when not using developer mode in Windows or macOS.",
- "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your clock isn't accurate.",
- "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.",
- "settings_error_bundled_tor_broken": "Something is wrong with OnionShare connecting to Tor in the background:\n{}",
- "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
- "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
+ "settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.",
+ "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.",
+ "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?",
+ "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
+ "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.",
+ "error_tor_protocol_error": "There was an error with Tor: {}",
+ "error_tor_protocol_error_unknown": "There was an unknown error with Tor",
+ "error_invalid_private_key": "This private key type is unsupported",
"connecting_to_tor": "Connecting to the Tor network",
- "update_available": "There is an OnionShare update available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
- "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.",
- "update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.",
- "update_not_available": "You are running the latest version of OnionShare.",
- "gui_tor_connection_ask": "Would you like to open OnionShare settings to troubleshoot connecting to Tor?",
- "gui_tor_connection_ask_open_settings": "Open Settings",
+ "update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.",
+ "update_error_check_error": "Could not check for new versions: The OnionShare website is saying the latest version is the unrecognizable '{}'…",
+ "update_error_invalid_latest_version": "Could not check for new version: Maybe you're not connected to Tor, or the OnionShare website is down?",
+ "update_not_available": "You are running the latest OnionShare.",
+ "gui_tor_connection_ask": "Open the settings to sort out connection to Tor?",
+ "gui_tor_connection_ask_open_settings": "Yes",
"gui_tor_connection_ask_quit": "Quit",
- "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.",
- "gui_tor_connection_canceled": "OnionShare cannot connect to Tor.\n\nMake sure you're connected to the internet, then re-open OnionShare to configure the Tor connection.",
+ "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.",
+ "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.",
"gui_tor_connection_lost": "Disconnected from Tor.",
- "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
- "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
- "share_via_onionshare": "Share via OnionShare",
- "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved address)",
- "gui_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using <b>Tor Browser</b>: <img src='{}' />",
- "gui_url_label_persistent": "This share will not stop automatically unless a timer is set.<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
- "gui_url_label_stay_open": "This share will not stop automatically unless a timer is set.",
- "gui_url_label_onetime": "This share will stop after the first download",
- "gui_url_label_onetime_and_persistent": "This share will stop after the first download<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
- "gui_status_indicator_stopped": "Ready to Share",
- "gui_status_indicator_working": "Starting...",
- "gui_status_indicator_started": "Sharing",
- "gui_file_info": "{} Files, {}",
- "gui_file_info_single": "{} File, {}",
- "info_in_progress_downloads_tooltip": "{} download(s) in progress",
- "info_completed_downloads_tooltip": "{} download(s) completed"
+ "gui_server_started_after_timeout": "The auto-stop timer ran out before the server started.\nPlease make a new share.",
+ "gui_server_timeout_expired": "The auto-stop timer already ran out.\nPlease update it to start sharing.",
+ "share_via_onionshare": "OnionShare it",
+ "gui_connect_to_tor_for_onion_settings": "Connect to Tor to see onion service settings",
+ "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
+ "gui_save_private_key_checkbox": "Use a persistent address",
+ "gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "This share will not auto-stop.<br><br>Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
+ "gui_url_label_stay_open": "This share will not auto-stop.",
+ "gui_url_label_onetime": "This share will stop after first completion.",
+ "gui_url_label_onetime_and_persistent": "This share will not auto-stop.<br><br>Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
+ "gui_status_indicator_share_stopped": "Ready to share",
+ "gui_status_indicator_share_working": "Starting…",
+ "gui_status_indicator_share_started": "Sharing",
+ "gui_status_indicator_receive_stopped": "Ready to receive",
+ "gui_status_indicator_receive_working": "Starting…",
+ "gui_status_indicator_receive_started": "Receiving",
+ "gui_file_info": "{} files, {}",
+ "gui_file_info_single": "{} file, {}",
+ "history_in_progress_tooltip": "{} in progress",
+ "history_completed_tooltip": "{} completed",
+ "error_cannot_create_data_dir": "Could not create OnionShare data folder: {}",
+ "receive_mode_data_dir": "Files sent to you appear in this folder: {}",
+ "receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.",
+ "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
+ "receive_mode_upload_starting": "Upload of total size {} is starting",
+ "receive_mode_received_file": "Received: {}",
+ "gui_mode_share_button": "Share Files",
+ "gui_mode_receive_button": "Receive Files",
+ "gui_settings_receiving_label": "Receiving settings",
+ "gui_settings_data_dir_label": "Save files to",
+ "gui_settings_data_dir_browse_button": "Browse",
+ "gui_settings_public_mode_checkbox": "Public mode",
+ "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}",
+ "gui_settings_language_label": "Preferred language",
+ "gui_settings_language_changed_notice": "Restart OnionShare for your change in language to take effect.",
+ "systray_menu_exit": "Quit",
+ "systray_page_loaded_title": "Page Loaded",
+ "systray_page_loaded_message": "OnionShare address loaded",
+ "systray_share_started_title": "Sharing Started",
+ "systray_share_started_message": "Starting to send files to someone",
+ "systray_share_completed_title": "Sharing Complete",
+ "systray_share_completed_message": "Finished sending files",
+ "systray_share_canceled_title": "Sharing Canceled",
+ "systray_share_canceled_message": "Someone canceled receiving your files",
+ "systray_receive_started_title": "Receiving Started",
+ "systray_receive_started_message": "Someone is sending files to you",
+ "gui_all_modes_history": "History",
+ "gui_all_modes_clear_history": "Clear All",
+ "gui_all_modes_transfer_started": "Started {}",
+ "gui_all_modes_transfer_finished_range": "Transferred {} - {}",
+ "gui_all_modes_transfer_finished": "Transferred {}",
+ "gui_all_modes_transfer_canceled_range": "Canceled {} - {}",
+ "gui_all_modes_transfer_canceled": "Canceled {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} elapsed.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (calculating)",
+ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_share_mode_no_files": "No Files Sent Yet",
+ "gui_share_mode_timeout_waiting": "Waiting to finish sending",
+ "gui_receive_mode_no_files": "No Files Received Yet",
+ "gui_receive_mode_timeout_waiting": "Waiting to finish receiving"
}
diff --git a/share/locale/eo.json b/share/locale/eo.json
index 6d904dc0..bf578276 100644
--- a/share/locale/eo.json
+++ b/share/locale/eo.json
@@ -1,23 +1,15 @@
{
"config_onion_service": "Agordas onion service je pordo {0:d}.",
"preparing_files": "Preparas dosierojn por kundivido.",
- "wait_for_hs": "Atendas al hidden sevice por esti preta:",
- "wait_for_hs_trying": "Provas...",
- "wait_for_hs_nope": "Ankoraŭ ne preta.",
- "wait_for_hs_yup": "Preta!",
"give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:",
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
- "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon",
+ "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon",
"not_a_file": "{0:s} ne estas dosiero.",
- "download_page_loaded": "Download page loaded",
- "other_page_loaded": "URL loaded",
+ "other_page_loaded": "",
"closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis",
"large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn",
- "error_tails_invalid_port": "Malĝusta valoro, pordo-numero devas esti plena numero",
- "error_tails_unknown_root": "Nekonata eraro kun Tails-root-procezo",
"help_local_only": "Ne strebu uzi tor: nur por evoluado",
"help_stay_open": "Lasu onion service funkcii post fino de elŝuto",
- "help_transparent_torification": "My system is transparently torified",
"help_stealth": "Create stealth onion service (advanced)",
"help_debug": "Protokoli erarojn sur disko",
"help_filename": "Listo de dosieroj aŭ dosierujoj por kundividi",
@@ -25,35 +17,27 @@
"gui_add": "Aldoni",
"gui_delete": "Forviŝi",
"gui_choose_items": "Elekti",
- "gui_start_server": "Komenci kundividon",
- "gui_stop_server": "Ĉesigi kundividon",
+ "gui_share_start_server": "Komenci kundividon",
+ "gui_share_stop_server": "Ĉesigi kundividon",
"gui_copy_url": "Kopii URL",
"gui_copy_hidservauth": "Kopii HidServAuth",
"gui_downloads": "Elŝutoj:",
"gui_canceled": "Nuligita",
"gui_copied_url": "URL kopiita en tondujon",
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
- "gui_starting_server1": "Startigas Tor onion service...",
- "gui_starting_server2": "Crunching files...",
"gui_please_wait": "Bonvolu atendi...",
- "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}",
- "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}",
- "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
- "gui_download_progress_complete": "%p%, Tempo pasinta: {0:s}",
- "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)",
- "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
"version_string": "Onionshare {0:s} | https://onionshare.org/",
- "gui_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.",
+ "gui_share_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.",
"gui_quit_warning_quit": "Foriri",
"gui_quit_warning_dont_quit": "Ne foriri",
"error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.",
- "zip_progress_bar_format": "Crunching files: %p%",
+ "zip_progress_bar_format": "Compressing files: %p%",
"error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
"error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.",
- "gui_menu_file_menu": "&File",
- "gui_menu_settings_action": "&Settings",
- "gui_menu_quit_action": "&Quit",
- "gui_settings_window_title": "Settings",
+ "gui_settings_window_title": "",
"gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?",
"gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser",
"gui_settings_connection_type_control_port_option": "Konekti per kontrolpordo",
@@ -61,12 +45,9 @@
"gui_settings_control_port_label": "Kontrolpordo",
"gui_settings_socket_file_label": "Socket-dosiero",
"gui_settings_authenticate_label": "Tor authentication options",
- "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
+ "gui_settings_authenticate_no_auth_option": "",
"gui_settings_authenticate_password_option": "Pasvorto",
- "gui_settings_authenticate_cookie_option": "Kuketo",
"gui_settings_password_label": "Pasvorto",
- "gui_settings_cookie_label": "Cookie path",
- "gui_settings_button_test": "Test Settings",
"gui_settings_button_save": "Konservi",
"gui_settings_button_cancel": "Nuligi",
"settings_saved": "Agordoj konservitaj en {}",
@@ -74,8 +55,8 @@
"settings_error_automatic": "Ne eblas konektiĝi al Tor-kontrolilo. Ĉu Tor Browser funkcias en la fono? Se vi ne havas ĝin, vi povas ekhavi ĝin je:\nhttps://www.torproject.org/.",
"settings_error_socket_port": "Ne eblas konektiĝi al Tor-kontrolilo je {}:{}.",
"settings_error_socket_file": "Ne eblas konektiĝi al Tor-kontrolilo per socket-dosiero {}.",
- "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
- "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
"settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.",
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
"error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work."
diff --git a/share/locale/es.json b/share/locale/es.json
index 5d9f8dcd..9ad6fad5 100644
--- a/share/locale/es.json
+++ b/share/locale/es.json
@@ -1,32 +1,221 @@
{
- "connecting_ctrlport": "Conectando a puerto control de Tor para configurar servicio oculto en puerto {0:d}.",
- "cant_connect_ctrlport": "No se pudo conectar a puerto control de Tor en puertos {0:s}. ¿Está funcionando Tor?",
- "cant_connect_socksport": "No se pudo conectar al servidor SOCKS5 de Tor en el puerto {0:s}. ¿Está funcionando Tor?",
- "preparing_files": "Preparando los archivos para compartir.",
- "wait_for_hs": "Esperando a que HS esté listo:",
- "wait_for_hs_trying": "Probando...",
- "wait_for_hs_nope": "No está listo todavía.",
- "wait_for_hs_yup": "Listo!",
- "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:",
- "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor",
- "not_a_file": "{0:s} no es un archivo.",
- "download_page_loaded": "La página de descarga está lista.",
- "other_page_loaded": "La URL está lista.",
- "closing_automatically": "Apagando automáticamente porque la descarga finalizó",
- "error_tails_invalid_port": "Valor inválido, el puerto debe ser un entero",
- "error_tails_unknown_root": "Error desconocido en el proceso de Tails ejecutando como roo",
- "help_tails_port": "Sólo Tails: puerto para abrir en el firewall, al levantar el servicio oculto",
- "help_local_only": "No intentar usar Tor: sólo para desarrollo",
- "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado",
- "help_debug": "Guardar registro de errores en el disco",
+ "preparing_files": "Comprimiendo los archivos.",
+ "give_this_url": "Entrega esta URL al receptor:",
+ "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor",
+ "not_a_file": "{0:s} no es un archivo válido.",
+ "other_page_loaded": "La URL está lista",
+ "closing_automatically": "Detenido porque la transferencia se completó",
+ "help_local_only": "No usar Tor (sólo para desarrollo)",
+ "help_stay_open": "Continuar compartiendo luego que los archivos hayan sido enviados",
+ "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco",
"help_filename": "Lista de archivos o carpetas para compartir",
- "gui_drag_and_drop": "Arrastre\narchivos aquí",
+ "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir",
"gui_add": "Añadir",
"gui_delete": "Eliminar",
"gui_choose_items": "Elegir",
- "gui_start_server": "Encender el Servidor",
- "gui_stop_server": "Detener el Servidor",
+ "gui_share_start_server": "Comenzar a compartir",
+ "gui_share_stop_server": "Dejar de compartir",
"gui_copy_url": "Copiar URL",
- "gui_downloads": "Descargas:",
- "gui_copied_url": "Se copió la URL en el portapapeles"
+ "gui_downloads": "Historial de descargas",
+ "gui_copied_url": "Dirección OnionShare copiada al portapapeles",
+ "config_onion_service": "Configurando el servicio cebolla en el puerto {0:d}.",
+ "give_this_url_stealth": "Dale esta dirección y la línea de HidServAuth a la persona a la que le estás enviando el archivo:",
+ "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible",
+ "close_on_timeout": "Parado porque el temporizador expiró",
+ "timeout_download_still_running": "Esperando a que se complete la descarga",
+ "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas",
+ "help_shutdown_timeout": "Dejar de compartir después de una determinada cantidad de segundos",
+ "help_stealth": "Usar autorización de cliente (avanzada)",
+ "help_config": "Ubicación del archivo de configuración JSON personalizado (opcional)",
+ "gui_copied_url_title": "Dirección de OnionShare copiada",
+ "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles",
+ "gui_please_wait": "Comenzando.... Haz clic para cancelar.",
+ "gui_quit_title": "No tan rápido",
+ "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarla, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.",
+ "zip_progress_bar_format": "Comprimiendo: %p%",
+ "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.",
+ "gui_settings_window_title": "Configuración",
+ "gui_settings_stealth_option": "Utilizar autorización de cliente",
+ "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes hacer clic para copiar tu HidServAuth.",
+ "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva",
+ "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible",
+ "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión",
+ "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare",
+ "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor",
+ "gui_settings_connection_type_test_button": "Probar la conexión a Tor",
+ "gui_settings_tor_bridges": "Soporte de puentes de Tor",
+ "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.",
+ "settings_saved": "Ajustes guardados en {}",
+ "give_this_url_receive": "Dele esta dirección al remitente:",
+ "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:",
+ "not_a_readable_file": "{0:s} no es un archivo legible.",
+ "systray_menu_exit": "Salir",
+ "systray_download_started_title": "Iniciada la descarga de OnionShare",
+ "systray_download_started_message": "Alguien comenzó a descargar tus archivos",
+ "systray_download_completed_title": "Descarga de OnionShare finalizada",
+ "systray_download_completed_message": "Alguien ha terminado de descargar tus archivos",
+ "systray_download_canceled_title": "Descarga de OnionShare Cancelada",
+ "systray_download_canceled_message": "El usuario canceló la descarga",
+ "systray_upload_started_title": "Subida OnionShare Iniciada",
+ "systray_upload_started_message": "Un usuario comenzó a subir archivos a tu computadora",
+ "help_receive": "Recibir recursos compartidos en lugar de enviarlos",
+ "gui_share_stop_server_shutdown_timeout": "Dejar de Compartir ({}s restantes)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}",
+ "gui_receive_start_server": "Iniciar el modo de recepción",
+ "gui_receive_stop_server": "Detener el modo de recepción",
+ "gui_receive_stop_server_shutdown_timeout": "Detener el modo de recepción ({}s restantes)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}",
+ "gui_copy_hidservauth": "Copiar HidServAuth",
+ "gui_no_downloads": "Ninguna Descarga Todavía",
+ "gui_canceled": "Cancelado",
+ "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth",
+ "settings_error_unknown": "No se puede conectar al controlador Tor porque tu configuración no tiene sentido.",
+ "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?",
+ "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.",
+ "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.",
+ "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?",
+ "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.",
+ "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o tu usuario no tiene permiso para leer el archivo cookie.",
+ "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.",
+ "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?",
+ "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}",
+ "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}.\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.",
+ "error_tor_protocol_error": "Hubo un error con Tor: {}",
+ "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor",
+ "error_invalid_private_key": "Este tipo de clave privada no está soportado",
+ "connecting_to_tor": "Conectando a la red de Tor",
+ "update_available": "Hay un nuevo OnionShare. <a href='{}'>Haz clic aquí</a> para obtenerlo.<br><br>Estás usando {} y el último es {}.",
+ "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…",
+ "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?",
+ "update_not_available": "Estás ejecutando la última versión de OnionShare.",
+ "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?",
+ "gui_tor_connection_ask_open_settings": "Sí",
+ "gui_tor_connection_ask_quit": "Salir",
+ "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.",
+ "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.",
+ "gui_tor_connection_lost": "Desconectado de Tor.",
+ "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea una nueva conexión compartida.",
+ "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.",
+ "share_via_onionshare": "Compártelo con OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas",
+ "gui_save_private_key_checkbox": "Usar una dirección persistente",
+ "gui_share_url_description": "<b>Cualquier persona </b> con esta dirección de OnionShare puede <b>descargar</b> tus archivos usando el <b>Navegador Tor</b>: <img src='{}' />",
+ "gui_receive_url_description": "Con esta dirección de OnionShare, <b>cualquier persona</b> puede <b>subir</b> archivos a tu ordenador usando el <b>Navegador Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.<br><br>Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)",
+ "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.",
+ "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.",
+ "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.<br><br>Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)",
+ "gui_status_indicator_share_stopped": "Listo para compartir",
+ "gui_status_indicator_share_working": "Comenzando.…",
+ "gui_status_indicator_share_started": "Compartir",
+ "gui_status_indicator_receive_stopped": "Listo para recibir",
+ "gui_status_indicator_receive_working": "Comenzando.…",
+ "gui_status_indicator_receive_started": "Recibiendo",
+ "gui_file_info": "{} archivos, {}",
+ "gui_file_info_single": "{} archivo, {}",
+ "info_in_progress_downloads_tooltip": "{} descarga(s) en curso",
+ "info_completed_downloads_tooltip": "{} descarga(s) completada(s)",
+ "info_in_progress_uploads_tooltip": "{} subida(s) en curso",
+ "info_completed_uploads_tooltip": "{} subida(s) completada(s)",
+ "receive_mode_downloads_dir": "Los archivos que te envíen aparecerán en esta carpeta: {}",
+ "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a tu ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíe, o si sabes lo que estás haciendo.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)",
+ "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?",
+ "gui_quit_warning_quit": "Salir",
+ "gui_quit_warning_dont_quit": "Cancelar",
+ "gui_settings_whats_this": "<a href='{0:s}'>¿Qué es esto?</a>",
+ "gui_settings_autoupdate_timestamp": "Última comprobación: {}",
+ "gui_settings_autoupdate_timestamp_never": "Nunca",
+ "gui_settings_general_label": "Ajustes generales",
+ "gui_settings_sharing_label": "Configuración de compartición",
+ "gui_settings_close_after_first_download_option": "Dejar de compartir luego que los archivos hayan sido enviados",
+ "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?",
+ "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control",
+ "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket",
+ "gui_settings_control_port_label": "Puerto de control",
+ "gui_settings_socket_file_label": "Archivo Socket",
+ "gui_settings_socks_label": "Puerto SOCKS",
+ "gui_settings_authenticate_label": "Configuración de autenticación Tor",
+ "gui_settings_authenticate_no_auth_option": "Sin autenticación, o autenticación por cookies",
+ "gui_settings_authenticate_password_option": "Contraseña",
+ "gui_settings_password_label": "Contraseña",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes",
+ "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes insertables obfs4 incorporados",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes insertables obfs4 incorporados (requiere obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte insertable incorporado meek_lite (Azure)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte insertable meek_lite (Azure) incorporado (requiere obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son muy costosos de correr para el Proyecto Tor. <br><br>Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.",
+ "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados",
+ "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_button_save": "Guardar",
+ "gui_settings_button_cancel": "Cancelar",
+ "gui_settings_button_help": "Ayuda",
+ "gui_settings_shutdown_timeout_checkbox": "Usar temporizador de parada automática",
+ "gui_settings_shutdown_timeout": "Detener carpeta compartida en:",
+ "history_in_progress_tooltip": "{} en progreso",
+ "history_completed_tooltip": "{} completado",
+ "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}",
+ "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}",
+ "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.<br><br><b>Algunos archivos pueden potencialmente tomar el control de tu ordenador si los abres. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.</b>",
+ "receive_mode_upload_starting": "La subida con un tamaño total de {} está comenzando ahora",
+ "receive_mode_received_file": "Recibido: {}",
+ "gui_mode_share_button": "Compartir archivos",
+ "gui_mode_receive_button": "Recibir archivos",
+ "gui_settings_receiving_label": "Ajustes de recepción",
+ "gui_settings_downloads_label": "Guardar archivos en",
+ "gui_settings_downloads_button": "Examinar",
+ "gui_settings_public_mode_checkbox": "Modo público",
+ "systray_close_server_title": "Servidor OnionShare cerrado",
+ "systray_close_server_message": "Un usuario cerró el servidor",
+ "systray_page_loaded_title": "Página Cargada",
+ "systray_download_page_loaded_message": "Un usuario cargó la página de descarga",
+ "systray_upload_page_loaded_message": "Un usuario cargó la página de carga",
+ "gui_uploads": "Historial de carga",
+ "gui_no_uploads": "No hay subidas todavía",
+ "gui_clear_history": "Limpiar todo",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "El modo de recepción puede ser detenido por el remitente",
+ "gui_upload_in_progress": "Subida Iniciada {}",
+ "gui_upload_finished": "Subido {}",
+ "gui_download_in_progress": "Descarga iniciada {}",
+ "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}",
+ "gui_settings_language_label": "Idioma preferido",
+ "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.",
+ "gui_upload_finished_range": "Cargado {} a {}",
+ "timeout_upload_still_running": "Esperando a que se complete la subida",
+ "gui_add_files": "Añadir Archivos",
+ "gui_add_folder": "Añadir Carpeta",
+ "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla",
+ "error_cannot_create_data_dir": "No se pudo crear carpeta de datos OnionShare: {}",
+ "receive_mode_data_dir": "Archivos enviados a usted aparecen en esta carpeta: {}",
+ "gui_settings_data_dir_label": "Guardar archivos en",
+ "gui_settings_data_dir_browse_button": "Navegar",
+ "systray_page_loaded_message": "Dirección OnionShare cargada",
+ "systray_share_started_title": "Compartir Iniciado",
+ "systray_share_started_message": "Se empezó a enviar archivos a alguien",
+ "systray_share_completed_title": "Compartir Completado",
+ "systray_share_completed_message": "Finalizó envío de archivos",
+ "systray_share_canceled_title": "Compartir Cancelado",
+ "systray_share_canceled_message": "Alguien canceló la recepción de sus archivos",
+ "systray_receive_started_title": "Recepción Iniciada",
+ "systray_receive_started_message": "Alguien le está enviando archivos",
+ "gui_all_modes_history": "Historial",
+ "gui_all_modes_clear_history": "Limpiar Todo",
+ "gui_all_modes_transfer_started": "Iniciado {}",
+ "gui_all_modes_transfer_finished_range": "Transferido {} - {}",
+ "gui_all_modes_transfer_finished": "Transferido {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} transcurridos.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (calculando)",
+ "gui_all_modes_progress_eta": "{0:s}, TEA: {1:s}, %p%",
+ "gui_share_mode_no_files": "No se enviaron archivos todavía",
+ "gui_share_mode_timeout_waiting": "Esperando a que termine el envío",
+ "gui_receive_mode_no_files": "No se recibieron archivos todavía",
+ "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción",
+ "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}",
+ "gui_all_modes_transfer_canceled": "Cancelado {}",
+ "gui_settings_onion_label": "Configuración de Onion"
}
diff --git a/share/locale/fa.json b/share/locale/fa.json
new file mode 100644
index 00000000..7e6c305c
--- /dev/null
+++ b/share/locale/fa.json
@@ -0,0 +1,216 @@
+{
+ "config_onion_service": "آماده سازی سرویس onion روی پورت {0:d}.",
+ "preparing_files": "فشرده سازی فایل ها.",
+ "give_this_url": "دادن این آدرس به گیرنده:",
+ "give_this_url_stealth": "دادن این آدرس و خط HidServAuth به گیرنده:",
+ "give_this_url_receive": "دادن این آدرس به ارسال کننده:",
+ "give_this_url_receive_stealth": "دادن این آدرس و HidServAuth به ارسال کننده:",
+ "ctrlc_to_stop": "برای توقف سرور Ctrl+C را فشار دهید",
+ "not_a_file": "{0:s} یک فایل معتبر نمی باشد.",
+ "not_a_readable_file": "{0:s} قابل خواندن نمی باشد.",
+ "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد",
+ "other_page_loaded": "آدرس بارگذاری شد",
+ "close_on_timeout": "متوقف شد چون تایمر توقف خودکار به پایان رسید",
+ "closing_automatically": "متوقف شد چون انتقال انجام شد",
+ "timeout_download_still_running": "انتظار برای تکمیل دانلود",
+ "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد",
+ "systray_menu_exit": "خروج",
+ "systray_download_started_title": "دانلود OnionShare آغاز شد",
+ "systray_download_started_message": "یک کاربر شروع به دانلود فایل های شما کرد",
+ "systray_download_completed_title": "دانلود OnionShare به پایان رسید",
+ "systray_download_completed_message": "دانلود فایل های شما توسط کاربر به پایان رسید",
+ "systray_download_canceled_title": "دانلود OnionShare لغو شد",
+ "systray_download_canceled_message": "کاربر دانلود را لغو کرد",
+ "systray_upload_started_title": "آپلود OnionShare آغاز شد",
+ "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است",
+ "help_local_only": "عدم استفاده از Tor (فقط برای توسعه)",
+ "help_stay_open": "ادامه اشتراک گذاری پس از ارسال دانلود ها",
+ "help_shutdown_timeout": "توقف به اشتراک گذاری پس از میزان ثانیه ای مشخص",
+ "help_stealth": "استفاده از احراز هویت کلاینت (پیشرفته)",
+ "help_receive": "دریافت اشتراک به جای ارسال آن",
+ "help_debug": "لاگ کردن خطاهای OnionShare روی stdout، و خطاهای وب بر روی دیسک",
+ "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری",
+ "help_config": "مکان فایل کانفیگ JSON کاستوم (اختیاری)",
+ "gui_drag_and_drop": "فایل ها و پوشه ها را بکشید و رها کنید\nتا اشتراک گذاری آغاز شود",
+ "gui_add": "افزودن",
+ "gui_delete": "حذف",
+ "gui_choose_items": "انتخاب",
+ "gui_share_start_server": "شروع اشتراک گذاری",
+ "gui_share_stop_server": "توقف اشتراک گذاری",
+ "gui_share_stop_server_shutdown_timeout": "توقف اشتراک گذاری ({} ثانیه باقیمانده)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} متوقف می شود",
+ "gui_receive_start_server": "شروع حالت دریافت",
+ "gui_receive_stop_server": "توقف حالت دریافت",
+ "gui_receive_stop_server_shutdown_timeout": "توقف حالت دریافت ({} ثانیه باقیمانده)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} به پایان می رسد",
+ "gui_copy_url": "کپی آدرس",
+ "gui_copy_hidservauth": "کپی HidServAuth",
+ "gui_downloads": "دانلود تاریخچه",
+ "gui_no_downloads": "",
+ "gui_canceled": "لغو شده",
+ "gui_copied_url_title": "آدرس OnionShare کپی شد",
+ "gui_copied_url": "آدرس OnionShare بر کلیپ بورد کپی شد",
+ "gui_copied_hidservauth_title": "HidServAuth کپی شد",
+ "gui_copied_hidservauth": "خط HidServAuth بر کلیپ بورد کپی شد",
+ "gui_please_wait": "در حال آغاز... برای لغو کلیک کنید.",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "نه به این سرعت",
+ "gui_share_quit_warning": "شما در پروسه ارسال فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟",
+ "gui_receive_quit_warning": "شما در پروسه دریافت فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟",
+ "gui_quit_warning_quit": "خروج",
+ "gui_quit_warning_dont_quit": "لغو",
+ "error_rate_limit": "شخصی تعداد زیادی قصد ناصحیح روی آدرس شما داشته است، این می تواند بدین معنا باشد که در حال تلاش برای حدس زدن آن هستند، بنابراین OnionShare سرور را متوقف کرده است. دوباره اشتراک گذاری را آغاز کنید و به گیرنده یک آدرس جدید برای اشتراک ارسال کنید.",
+ "zip_progress_bar_format": "فشرده سازی: %p%",
+ "error_stealth_not_supported": "برای استفاده از احراز هویت کلاینت، شما نیاز به داشتن Tor 0.2.9.1-alpha (یا مرورگر Tor 6.5) و python3-stem 1.5.0 دارید.",
+ "error_ephemeral_not_supported": "OnionShare حداقل به Tor 0.2.7.1 و python3-stem 1.4.0 نیاز دارد.",
+ "gui_settings_window_title": "تنظیمات",
+ "gui_settings_whats_this": "<a href='{0:s}'>این چیست؟</a>",
+ "gui_settings_stealth_option": "استفاده از احراز هویت کلاینت",
+ "gui_settings_stealth_hidservauth_string": "ذخیره کردن کلید خصوصی برای استفاده دوباره، بدین معناست که الان می توانید برای کپی HidServAuth کلیک کنید.",
+ "gui_settings_autoupdate_label": "بررسی برای نسخه جدید",
+ "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن",
+ "gui_settings_autoupdate_timestamp": "آخرین بررسی: {}",
+ "gui_settings_autoupdate_timestamp_never": "هرگز",
+ "gui_settings_autoupdate_check_button": "بررسی برای نسخه جدید",
+ "gui_settings_general_label": "تنظیمات کلی",
+ "gui_settings_sharing_label": "تنظیمات اشتراک گذاری",
+ "gui_settings_close_after_first_download_option": "توقف اشتراک گذاری پس از اولین ارسال دانلود",
+ "gui_settings_connection_type_label": "OnionShare چگونه به Tor باید متصل شود؟",
+ "gui_settings_connection_type_bundled_option": "استفاده از نسخه Tor قرار گرفته در OnionShare",
+ "gui_settings_connection_type_automatic_option": "اعمال پیکربندی خودکار با مرورگر Tor",
+ "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل",
+ "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت",
+ "gui_settings_connection_type_test_button": "تست اتصال به Tor",
+ "gui_settings_control_port_label": "پورت کنترل",
+ "gui_settings_socket_file_label": "فایل سوکت‌",
+ "gui_settings_socks_label": "پورت SOCKS",
+ "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor",
+ "gui_settings_authenticate_no_auth_option": "هیچ احراز هویت، یا احراز هویت کوکی",
+ "gui_settings_authenticate_password_option": "رمز عبور",
+ "gui_settings_password_label": "رمز عبور",
+ "gui_settings_tor_bridges": "پشتیبانی بریج Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج",
+ "gui_settings_tor_bridges_obfs4_radio_option": "استفاده از پلاگبل ترنسپورت obfs4",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت obfs4 (نیازمند obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "استفاده از پلاگبل ترنسپورت meek_lite (Azure)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت meek_lite (Azure) (نیازمند obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "هشدار: بریج های meek_lite برای پروژه Tor بسیار هزینه بر هستند.<br><br> فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق obfs4، یا دیگر بریج ها از آن استفاده کنید.",
+ "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم",
+ "gui_settings_tor_bridges_custom_label": "میتوانید از <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a> بریج دریافت کنید",
+ "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.",
+ "gui_settings_button_save": "ذخیره",
+ "gui_settings_button_cancel": "لغو",
+ "gui_settings_button_help": "راهنما",
+ "gui_settings_shutdown_timeout_checkbox": "استفاده از تایمر توقف خودکار",
+ "gui_settings_shutdown_timeout": "توقف اشتراک در:",
+ "settings_error_unknown": "ناتوانی در اتصال به کنترل کننده Tor بدلیل نامفهوم بودن تنظیمات.",
+ "settings_error_automatic": "ناتوانی در اتصال به کنترل کننده Tor. آیا مرورگر Tor (در دسترس از طریق torproject.org) در پس زمینه در حال اجراست؟",
+ "settings_error_socket_port": "ناتوانی در اتصال به کنترل کننده Tor در {}:{}.",
+ "settings_error_socket_file": "ناتوانی در اتصال به کنترل کننده Tor از طریق فایل سوکت {}.",
+ "settings_error_auth": "متصل به {}:{}، اما ناتوانی در احراز هویت. شاید این یک کنترل کننده Tor نمی باشد؟",
+ "settings_error_missing_password": "متصل به کنترل کننده Tor، اما نیاز به یک رمز عبور برای احراز هویت می باشد.",
+ "settings_error_unreadable_cookie_file": "اتصال به کنترل کننده Tor برقرار است، اما رمز عبور ممکن است اشتباه باشد، یا کاربری شما اجازه خواندن فایل کوکی را ندارد.",
+ "settings_error_bundled_tor_not_supported": "استفاده از نسخه Tor که با OnionShare می آید در حالت توسعه روی ویندوز یا مک کار نمی کند.",
+ "settings_error_bundled_tor_timeout": "اتصال به Tor زمان زیادی می برد. شاید شما به اینترنت متصل نیستید، یا ساعت سیستم شما دقیق نیست؟",
+ "settings_error_bundled_tor_broken": "OnionShare نمی تواند در پس زمینه به Tor متصل شود:\n{}",
+ "settings_test_success": "اتصال به کنترل کننده Tor برقرار است.\n\nنسخه Tor: {}\nسرویس های onion ناپایدار پشتیبانی شده: {}.\nاحراز هویت کلاینت پشتیبانی شده: {}.\nپشتیبانی از آدرس های .onion نسل بعدی: {}.",
+ "error_tor_protocol_error": "خطایی با Tor وجود داشت: {}",
+ "error_tor_protocol_error_unknown": "خطای ناشناخته ای با Tor وجود داشت",
+ "error_invalid_private_key": "این نوع کلید خصوصی پشتیبانی نمی شود",
+ "connecting_to_tor": "در حال اتصال به شبکه Tor",
+ "update_available": "نسخه جدید OnionShare وجود دارد. <a href='{}'> اینجا کلیک کنید</a> تا آن را دریافت کنید.<br><br> شما در حال استفاده از {} می باشید و آخرین نسخه {} می باشد.",
+ "update_error_check_error": "ناتوانی در بررسی برای نسخه های جدید: سایت OnionShare میگوید که آخرین نسخه '{}' ناشناس می باشد…",
+ "update_error_invalid_latest_version": "ناتوانی در بررسی نسخه جدید: شاید شما به Tor متصل نیستید، یا سایت OnionShare کار نمی کند؟",
+ "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.",
+ "gui_tor_connection_ask": "باز کردن تنظیمات برای ساماندهی اتصال به Tor؟",
+ "gui_tor_connection_ask_open_settings": "بله",
+ "gui_tor_connection_ask_quit": "خروج",
+ "gui_tor_connection_error_settings": "تغییر نحوه اتصال OnionShare به شبکه Tor در تنظیمات.",
+ "gui_tor_connection_canceled": "اتصال به Tor برقرار نشد.\n\nمطمئن شوید که به اینترنت متصل هستید، سپس OnionShare را دوباره باز کرده و اتصال آن را به Tor دوباره برقرار کنید.",
+ "gui_tor_connection_lost": "اتصال با Tor قطع شده است.",
+ "gui_server_started_after_timeout": "تایمر توقف خودکار قبل از آغاز سرور به پایان رسید.\nلطفا یک اشتراک جدید درست کنید.",
+ "gui_server_timeout_expired": "تایمر توقف خودکار به پایان رسید.\nلطفا برای آغاز اشتراک گذاری آن را به روز رسانی کنید.",
+ "share_via_onionshare": "OnionShare کنید",
+ "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده",
+ "gui_save_private_key_checkbox": "استفاده از یک آدرس پایا",
+ "gui_share_url_description": "<b>هرکس</b> با این آدرس OnionShare میتواند روی کامپیوتر شما فایل <b>دانلود</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>هرکس</b> با این آدرس OnionShare میتواند روی کامپیوتر شما فایل <b>آپلود</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
+ "gui_url_label_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.<br><br>هر اشتراک بعدی از آدرس دوباره استفاده می کند. ( برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)",
+ "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.",
+ "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.",
+ "gui_url_label_onetime_and_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.<br><br> هر اشتراک بعدی از آدرس دوباره استفاده میکند. (برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)",
+ "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری",
+ "gui_status_indicator_share_working": "در حال شروع…",
+ "gui_status_indicator_share_started": "در حال اشتراک گذاری",
+ "gui_status_indicator_receive_stopped": "آماده دریافت",
+ "gui_status_indicator_receive_working": "در حال شروع…",
+ "gui_status_indicator_receive_started": "درحال دریافت",
+ "gui_file_info": "{} فایل ها، {}",
+ "gui_file_info_single": "{} فایل، {}",
+ "history_in_progress_tooltip": "{} در حال انجام",
+ "history_completed_tooltip": "{} کامل شد",
+ "info_in_progress_uploads_tooltip": "{} آپلود در حال انجام",
+ "info_completed_uploads_tooltip": "{} آپلود کامل شد",
+ "error_cannot_create_downloads_dir": "ناتوانی در ایجاد پوشه حالت دریافت: {}",
+ "receive_mode_downloads_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}",
+ "receive_mode_warning": "هشدار: حالت دریافت به سایر افراد اجازه می دهد تا به روی کامپیوتر شما فایل آپلود کنند. برخی فایل ها را اگر باز کنید پتانسیل آن را دارند تا کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی که از کسانی دریافت کردید که به آن ها اعتماد دارید را باز کنید، یا اگر میدانید دارید چه کار میکنید.",
+ "gui_receive_mode_warning": "حالت دریافت به سایر افراد اجازه می دهد تا روی کامپیوتر شما فایل آپلود کنند.<br><br><b>برخی فایل ها را اگر باز کنید پتانسیل این را دارند که کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی را باز کنید که از کسانی دریافت کرده اید که به آن ها اعتماد دارید، یا میدانید دارید چه کار میکنید.</b>",
+ "receive_mode_upload_starting": "آپلود حجم کلی {} در حال آغاز می باشد",
+ "receive_mode_received_file": "دریافت شده: {}",
+ "gui_mode_share_button": "اشتراک گذاری فایل ها",
+ "gui_mode_receive_button": "دریافت فایل ها",
+ "gui_settings_receiving_label": "تنظیمات دریافت",
+ "gui_settings_downloads_label": "ذخیره فایل ها در",
+ "gui_settings_downloads_button": "فهرست",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "حالت عمومی",
+ "systray_close_server_title": "سرور OnionShare بسته شد",
+ "systray_close_server_message": "یک کاربر سرور را بست",
+ "systray_page_loaded_title": "صفحه بارگذاری شد",
+ "systray_download_page_loaded_message": "یک کاربر صفحه دانلود را بارگذاری کرد",
+ "systray_upload_page_loaded_message": "یک کاربر صفحه آپلود را بارگذاری کرد",
+ "gui_uploads": "تاریخچه آپلود",
+ "gui_no_uploads": "هیچ آپلودی هنوز وجود ندارد",
+ "gui_clear_history": "پاکسازی همه",
+ "gui_upload_in_progress": "آپلود آغاز شد {}",
+ "gui_upload_finished_range": "{} به {} آپلود شد",
+ "gui_upload_finished": "{} آپلود شد",
+ "gui_download_in_progress": "دانلود آغاز شد {}",
+ "gui_open_folder_error_nautilus": "ناتوانی در باز کردن پوشه به دلیل موجود نبودن ناتیلوس. فایل در اینجا قرار دارد: {}",
+ "gui_settings_language_label": "زبان ترجیحی",
+ "gui_settings_language_changed_notice": "ری استارت OnionShare برای دیدن نتیجه اعمال تغییر در زبان.",
+ "timeout_upload_still_running": "انتظار برای تکمیل آپلود",
+ "gui_add_files": "افزودن فایل ها",
+ "gui_add_folder": "افزودن پوشه",
+ "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion",
+ "error_cannot_create_data_dir": "ناتوانی در ایجاد پوشه داده OnionShare: {}",
+ "receive_mode_data_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}",
+ "gui_settings_data_dir_label": "ذخیره فایل ها در",
+ "gui_settings_data_dir_browse_button": "مرور",
+ "systray_page_loaded_message": "آدرس OnionShare بارگذاری شد",
+ "systray_share_started_title": "اشتراک گذاری آغاز شد",
+ "systray_share_started_message": "آغاز ارسال فایل به شخصی",
+ "systray_share_completed_title": "اشتراک گذاری تکمیل شد",
+ "systray_share_completed_message": "ارسال فایل ها به پایان رسید",
+ "systray_share_canceled_title": "اشتراک گذاری لغو شد",
+ "systray_share_canceled_message": "شخصی دریافت فایل های شما را لغو کرد",
+ "systray_receive_started_title": "دریافت آغاز شد",
+ "systray_receive_started_message": "شخصی در حال ارسال فایل به شماست",
+ "gui_all_modes_history": "تاریخچه",
+ "gui_all_modes_clear_history": "پاکسازی همه",
+ "gui_all_modes_transfer_started": "{} آغاز شد",
+ "gui_all_modes_transfer_finished_range": "{} - {} منتقل شد",
+ "gui_all_modes_transfer_finished": "{} منتقل شد",
+ "gui_all_modes_progress_complete": "%p%، {0:s} سپری شد.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (در حال محاسبه)",
+ "gui_all_modes_progress_eta": "{0:s}، تخمین: {1:s}, %p%",
+ "gui_share_mode_no_files": "هیچ فایلی هنوز ارسال نشده است",
+ "gui_share_mode_timeout_waiting": "انتظار برای به پایان رسیدن ارسال",
+ "gui_receive_mode_no_files": "هیچ فایلی هنوز دریافت نشده است",
+ "gui_receive_mode_timeout_waiting": "انتظار برای به پایان رسیدن دریافت",
+ "gui_all_modes_transfer_canceled_range": "{} - {} لغو شد",
+ "gui_all_modes_transfer_canceled": "{} لغو شد"
+}
diff --git a/share/locale/fi.json b/share/locale/fi.json
index 25fda84b..7f31450f 100644
--- a/share/locale/fi.json
+++ b/share/locale/fi.json
@@ -1,43 +1,25 @@
{
- "connecting_ctrlport": "Yhdistetään Torin ohjausporttiin että saadaan salattu palvelin porttiin {0:d}.",
- "cant_connect_ctrlport": "Ei voi yhdistää Torin ohjausporttiin portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.",
- "cant_connect_socksport": "Ei voi yhdistää Tor SOCKS5 palveluun portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.",
"preparing_files": "Valmistellaan tiedostoja jaettavaksi.",
- "wait_for_hs": "Odotetaan piilopalvelun valmistumista:",
- "wait_for_hs_trying": "Yritetään...",
- "wait_for_hs_nope": "Ei vielä valmis.",
- "wait_for_hs_yup": "Valmis!",
- "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:",
- "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen",
+ "give_this_url": "Anna tämä URL-osoite vastaanottajalle:",
+ "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen",
"not_a_file": "{0:s} Ei ole tiedosto.",
- "download_page_loaded": "Lataussivu ladattu",
"other_page_loaded": "URL-osoite ladattu",
"closing_automatically": "Lataus valmis. Suljetaan automaattisesti",
"large_filesize": "Varoitus: Isojen tiedostojen lähetys saattaa kestää tunteja",
- "error_tails_invalid_port": "Väärä arvo, portti pitää olla koknaisluku",
- "error_tails_unknown_root": "Tuntematon virhe Tailsissa",
- "help_tails_port": "Vain Tails: portti palomuurin läpi, käynnistetään salainen palvelin",
"help_local_only": "Älä käytä Toria: vain ohjelmakehitykseen",
"help_stay_open": "Pidä piilopalvelu käynnissä latauksen jälkeen.",
- "help_transparent_torification": "Järjestelmäni käyttää Toria läpinäkyvästi",
"help_debug": "Tallentaa virheet levylle",
"help_filename": "Luettele jaettavat tiedostot tai kansiot",
"gui_drag_and_drop": "Vedä ja pudota\ntiedostot tänne",
"gui_add": "Lisää",
"gui_delete": "Poista",
"gui_choose_items": "Valitse",
- "gui_start_server": "Käynnistä palvelin",
- "gui_stop_server": "Pysäytä palvelin",
+ "gui_share_start_server": "Käynnistä palvelin",
+ "gui_share_stop_server": "Pysäytä palvelin",
"gui_copy_url": "Kopioi URL-osoite",
"gui_downloads": "Lataukset:",
"gui_canceled": "Peruutettu",
"gui_copied_url": "URL-osoite kopioitu leikepöydälle",
- "gui_starting_server1": "Käynnistetään Tor piilopalvelu...",
- "gui_starting_server2": "Tiivistän tiedostoja...",
- "gui_starting_server3": "Odotetaan Tor piilopalvelua...",
"gui_please_wait": "Odota...",
- "error_hs_dir_cannot_create": "Piilopalvelulle ei pystytty luomaan hakemistoa {0:s}",
- "error_hs_dir_not_writable": "Piilopalvelun hakemistoon {0:s} ei voi kirjoittaa",
- "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua",
"zip_progress_bar_format": "Tiivistän tiedostoja: %p%"
}
diff --git a/share/locale/fr.json b/share/locale/fr.json
index a661cd26..8d87a501 100644
--- a/share/locale/fr.json
+++ b/share/locale/fr.json
@@ -1,43 +1,217 @@
{
- "connecting_ctrlport": "Connexion au réseau Tor pour mettre en place un onion service sur le port {0:d}.",
- "cant_connect_ctrlport": "Impossible de se connecter au port de contrôle Tor sur le port {0:s}. Est-ce que Tor tourne ?",
- "preparing_files": "Préparation des fichiers à partager.",
- "wait_for_hs": "En attente du HS:",
- "wait_for_hs_trying": "Tentative...",
- "wait_for_hs_nope": "Pas encore prêt.",
- "wait_for_hs_yup": "Prêt !",
- "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :",
- "ctrlc_to_stop": "Ctrl-C arrête le serveur",
- "not_a_file": "{0:s} n'est pas un fichier.",
- "download_page_loaded": "Page de téléchargement chargée",
- "other_page_loaded": "URL chargée",
- "closing_automatically": "Fermeture automatique car le téléchargement est fini",
- "error_tails_invalid_port": "Valeur invalide, le port doit être un nombre entier",
- "error_tails_unknown_root": "Erreur inconnue avec un processus root sur Tails",
+ "preparing_files": "Compression des fichiers.",
+ "give_this_url": "Donnez cette adresse au destinataire :",
+ "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur",
+ "not_a_file": "{0:s} n’est pas un fichier valide.",
+ "other_page_loaded": "L’adresse a été chargée",
+ "closing_automatically": "Arrêté, car le transfert est fini",
"systray_menu_exit": "Quitter",
- "systray_download_started_title": "Téléchargement OnionShare Démarré",
- "systray_download_started_message": "Un utilisateur télécharge vos fichiers",
- "systray_download_completed_title": "Téléchargement OnionShare Complete",
- "systray_download_canceled_title": "Téléchargement OnionShare Annulé",
- "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement",
- "help_tails_port": "Seulement sur Tails: port pour ouvrir le firewall, démarrage du onion service",
- "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement",
- "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini",
- "help_debug": "Enregistrer les erreurs sur le disque",
+ "systray_download_started_title": "Téléchargement OnionShare démarré",
+ "systray_download_started_message": "Une personne télécharge vos fichiers",
+ "systray_download_completed_title": "Téléchargement OnionShare terminé",
+ "systray_download_canceled_title": "Téléchargement OnionShare annulé",
+ "systray_download_canceled_message": "La personne a annulé le téléchargement",
+ "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)",
+ "help_stay_open": "Continuer le partage après l’envoi des fichiers",
+ "help_debug": "Journaliser les erreurs d’OnionShare sur la sortie standard et les erreurs Web sur le disque",
"help_filename": "Liste des fichiers ou dossiers à partager",
- "gui_drag_and_drop": "Glissez déposez\nles fichiers ici",
+ "gui_drag_and_drop": "Glisser-déposer des fichiers et dossiers\npour commencer le partage",
"gui_add": "Ajouter",
"gui_delete": "Supprimer",
- "gui_choose_items": "Sélectionnez",
- "gui_start_server": "Démarrer le serveur",
- "gui_stop_server": "Arrêter le serveur",
- "gui_copy_url": "Copier URL",
+ "gui_choose_items": "Sélectionner",
+ "gui_share_start_server": "Commencer le partage",
+ "gui_share_stop_server": "Arrêter le partage",
+ "gui_copy_url": "Copier l’adresse",
"gui_copy_hidservauth": "Copier HidServAuth",
- "gui_downloads": "Téléchargements :",
+ "gui_downloads": "Historique de téléchargement",
"gui_canceled": "Annulé",
- "gui_copied_url": "URL copié dans le presse-papier",
- "gui_please_wait": "Attendez-vous...",
+ "gui_copied_url": "L’adresse OnionShare a été copiée dans le presse-papiers",
+ "gui_please_wait": "Démarrage… Cliquez pour annuler.",
"gui_quit_warning_quit": "Quitter",
- "gui_quit_warning_dont_quit": "Ne quitter pas",
- "gui_settings_autoupdate_timestamp_never": "Jamais"
+ "gui_quit_warning_dont_quit": "Annuler",
+ "gui_settings_autoupdate_timestamp_never": "Jamais",
+ "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que la nouvelle langue soit appliquée.",
+ "config_onion_service": "Mise en place du service oignon sur le port {0:d}.",
+ "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :",
+ "give_this_url_receive": "Donnez cette adresse à l’expéditeur :",
+ "give_this_url_receive_stealth": "Donnez cette adresse et cette ligne HidServAuth à l'expéditeur :",
+ "not_a_readable_file": "{0:s} n’est pas un fichier lisible.",
+ "timeout_download_still_running": "En attente de la fin du téléchargement",
+ "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers",
+ "gui_copied_hidservauth_title": "HidServAuth a été copié",
+ "gui_settings_window_title": "Paramètres",
+ "gui_settings_autoupdate_timestamp": "Dernière vérification : {}",
+ "gui_settings_close_after_first_download_option": "Arrêter le partage après envoi des fichiers",
+ "gui_settings_connection_type_label": "Comment OnionShare devrait-il se connecter à Tor ?",
+ "gui_settings_connection_type_control_port_option": "Se connecter en utilisant le port de contrôle",
+ "gui_settings_connection_type_socket_file_option": "Se connecter en utilisant un fichier socket",
+ "gui_settings_socket_file_label": "Fichier socket",
+ "gui_settings_socks_label": "Port SOCKS",
+ "gui_settings_authenticate_no_auth_option": "Pas d’authentification ou authentification par témoin",
+ "gui_settings_authenticate_password_option": "Mot de passe",
+ "gui_settings_password_label": "Mot de passe",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de pont",
+ "gui_settings_button_save": "Enregistrer",
+ "gui_settings_button_cancel": "Annuler",
+ "gui_settings_button_help": "Aide",
+ "gui_settings_shutdown_timeout": "Arrêter le partage à :",
+ "connecting_to_tor": "Connexion au réseau Tor",
+ "help_config": "Emplacement du fichier personnalisé de configuration JSON (facultatif)",
+ "large_filesize": "Avertissement : envoyer un gros partage peut prendre des heures",
+ "gui_copied_hidservauth": "La ligne HidServAuth a été copiée dans le presse-papiers",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "zip_progress_bar_format": "Compression : %p%",
+ "error_ephemeral_not_supported": "OnionShare exige au moins Tor 0.2.7.1 et python3-stem 1.4.0.",
+ "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes",
+ "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.",
+ "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon",
+ "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)",
+ "systray_upload_started_title": "Envoi OnionShare démarré",
+ "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur",
+ "gui_no_downloads": "Pas encore de téléchargement",
+ "gui_copied_url_title": "L’adresse OnionShare a été copiée",
+ "gui_quit_title": "Pas si vite",
+ "gui_share_quit_warning": "Des fichiers sont en cours d’envoi. Voulez-vous vraiment quitter OnionShare ?",
+ "gui_receive_quit_warning": "Des fichiers sont en cours de réception. Voulez-vous vraiment quitter OnionShare ?",
+ "gui_settings_whats_this": "<a href='{0:s}'>Qu’est-ce que c’est ?</a>",
+ "gui_settings_autoupdate_label": "Vérifier les nouvelles versions",
+ "gui_settings_autoupdate_option": "Me signaler toute nouvelle version",
+ "gui_settings_general_label": "Paramètres généraux",
+ "gui_settings_sharing_label": "Paramètres de partage",
+ "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor intégrée dans OnionShare",
+ "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le Navigateur Tor",
+ "gui_settings_connection_type_test_button": "Tester la connexion à Tor",
+ "gui_settings_control_port_label": "Port de contrôle",
+ "gui_settings_authenticate_label": "Paramètres d’authentification de Tor",
+ "gui_settings_tor_bridges": "Prise en charge des ponts de Tor",
+ "gui_settings_tor_bridges_custom_radio_option": "Utiliser des ponts personnalisés",
+ "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des ponts sur <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Aucun des ponts que vous avez ajoutés ne fonctionne.\nVérifiez-les de nouveau ou ajoutez-en d’autres.",
+ "settings_error_unknown": "Impossible de se connecter au contrôleur Tor, car vos paramètres sont incorrects.",
+ "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Le Navigateur Tor (proposé sur torproject.org) fonctionne-t-il en arrière-plan ?",
+ "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.",
+ "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier socket {}.",
+ "settings_error_auth": "Vous êtes connecté à {}:{}, mais il est impossible de s’authentifier. Est-ce bien un contrôleur Tor ?",
+ "settings_error_missing_password": "Vous êtes connecté au contrôleur Tor, mais un mot de passe d’authentification est exigé.",
+ "settings_error_unreadable_cookie_file": "Vous êtes connecté au contrôleur Tor, mais le mot de passe est peut-être erroné ou votre utilisateur n’est pas autorisé à lire le fichier témoin.",
+ "settings_error_bundled_tor_not_supported": "La version de Tor intégrée dans OnionShare ne fonctionne pas en mode développeur sous Windows ou macOS.",
+ "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Êtes-vous connecté à Internet ? Votre horloge système est-elle mal réglée ?",
+ "settings_error_bundled_tor_broken": "OnionShare n’a pas réussi à se connecter à Tor en arrière-plan :\n{}",
+ "error_tor_protocol_error": "Une erreur est survenue avec Tor : {}",
+ "error_tor_protocol_error_unknown": "Une erreur inconnue est survenue avec Tor",
+ "error_invalid_private_key": "Ce type de clé privée n’est pas pris en charge",
+ "update_available": "Une nouvelle version d’OnionShare est proposée. <a href='{}'>Cliquez ici</a> pour l’obtenir.<br><br>Vous utilisez la version {} et {} est la dernière version.",
+ "update_not_available": "Vous utilisez la dernière version d’OnionShare.",
+ "gui_tor_connection_ask_open_settings": "Oui",
+ "gui_tor_connection_ask_quit": "Quitter",
+ "gui_tor_connection_lost": "Vous êtes déconnecté de Tor.",
+ "share_via_onionshare": "Partager avec OnionShare",
+ "gui_save_private_key_checkbox": "Utiliser une adresse persistante",
+ "gui_share_url_description": "<b>Quiconque</b> possède cette adresse OnionShare peut <b>télécharger</b> vos fichiers en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
+ "gui_receive_url_description": "<b>Quiconque</b> possède cette adresse OnionShare peut <b>téléverser</b> des fichiers vers votre ordinateur en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
+ "gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.<br><br>Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)",
+ "gui_url_label_stay_open": "Ce partage ne s’arrêtera pas automatiquement.",
+ "gui_url_label_onetime": "Ce partage s’arrêtera une fois que le premier téléchargement sera terminé.",
+ "gui_url_label_onetime_and_persistent": "Ce partage ne s’arrêtera pas automatiquement.<br><br>Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)",
+ "gui_status_indicator_share_stopped": "Prêt à partager",
+ "gui_status_indicator_share_working": "Démarrage…",
+ "gui_status_indicator_share_started": "Partage en cours",
+ "gui_status_indicator_receive_stopped": "Prêt à recevoir",
+ "gui_status_indicator_receive_working": "Démarrage…",
+ "gui_status_indicator_receive_started": "Réception en cours",
+ "gui_file_info": "{} fichiers, {}",
+ "gui_file_info_single": "{} fichier, {}",
+ "history_in_progress_tooltip": "{} en cours",
+ "history_completed_tooltip": "{} terminé",
+ "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}",
+ "receive_mode_warning": "Avertissement : Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur. Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.",
+ "gui_receive_mode_warning": "Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur.<br><br><b>Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.</b>",
+ "receive_mode_received_file": "Reçu : {}",
+ "gui_mode_share_button": "Partager des fichiers",
+ "gui_mode_receive_button": "Recevoir des fichiers",
+ "gui_settings_receiving_label": "Paramètres de réception",
+ "gui_settings_downloads_label": "Enregistrer les fichiers sous",
+ "gui_settings_downloads_button": "Parcourir",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Le mode réception peut-être arrêté par l'expéditeur",
+ "gui_settings_public_mode_checkbox": "Mode public",
+ "systray_close_server_title": "Serveur OnionShare arrêté",
+ "gui_uploads": "Historique d'envoi",
+ "gui_no_uploads": "Pas encore d'envoi",
+ "gui_clear_history": "Tout effacer",
+ "gui_upload_in_progress": "Envoi démarré {}",
+ "gui_upload_finished_range": "Envoyé {} de {}",
+ "gui_upload_finished": "{} envoyé",
+ "gui_download_in_progress": "Téléchargement démarré {}",
+ "gui_open_folder_error_nautilus": "Impossible d’ouvrir le dossier, car nautilus n’est pas disponible. Le fichier est ici : {}",
+ "gui_settings_language_label": "Langue préférée",
+ "help_stealth": "Utilisation de l’autorisation client (avancé)",
+ "help_receive": "Recevoir des partages au lieu de les envoyer",
+ "gui_receive_start_server": "Démarrer le mode réception",
+ "gui_receive_stop_server": "Arrêter le mode réception",
+ "gui_receive_stop_server_shutdown_timeout": "Arrêter le mode réception ({}s restantes)",
+ "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)",
+ "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%",
+ "error_rate_limit": "Quelqu’un a effectué trop de tentatives échouées sur votre adresse, ce qui signifie que cette personne pourrait essayer de la deviner. C’est pourquoi OnionShare a arrêté le serveur. Redémarrez le partage et envoyez au destinataire une nouvelle adresse pour partager.",
+ "error_stealth_not_supported": "Pour utiliser l’autorisation client, Tor 0.2.9.1-alpha (ou le Navigateur Tor 6.5) et python3-stem 1.5.0 ou versions ultérieures sont exigés.",
+ "gui_settings_stealth_option": "Utiliser l’autorisation client",
+ "timeout_upload_still_running": "En attente de la fin de l'envoi",
+ "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée pour qu’elle puisse être réutilisée,\nvous pouvez maintenant cliquer pour copier votre HidServAuth.",
+ "gui_settings_autoupdate_check_button": "Vérifier s’il existe une nouvelle version",
+ "settings_test_success": "Vous êtes connecté au contrôleur Tor.\n\nVersion de Tor : {}\nPrend en charge les services onion éphémères : {}.\nPrend en charge l’authentification client : {}.\nPrend en charge la nouvelle génération d’adresses .onion : {}.",
+ "update_error_check_error": "Impossible de vérifier l’existence d’une mise à jour : le site Web d’OnionShare indique que la dernière version ne peut pas être reconnue '{}'…",
+ "update_error_invalid_latest_version": "Impossible de vérifier l’existence d’une mise à jour : êtes-vous bien connecté à Tor, le site Web d’OnionShare est-il hors service ?",
+ "gui_tor_connection_ask": "Ouvrir les paramètres pour résoudre le problème de connexion à Tor ?",
+ "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nAssurez-vous d’être connecté à Internet, puis rouvrez OnionShare et configurez sa connexion à Tor.",
+ "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses héritées",
+ "info_in_progress_uploads_tooltip": "{} envoi(s) en cours",
+ "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)",
+ "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}",
+ "receive_mode_upload_starting": "Un téléversement d’une taille totale de {} commence",
+ "systray_close_server_message": "Une personne a arrêté le serveur",
+ "systray_page_loaded_title": "La page a été chargée",
+ "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement",
+ "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Utiliser les transports enfichables obfs4 intégrés",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (exige obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (exige obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.<br><br>Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor par les transports obfs4 ou autres ponts normaux.",
+ "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d’arrêt automatique",
+ "gui_server_started_after_timeout": "La minuterie d’arrêt automatique est arrivée au bout de son délai avant le démarrage du serveur.\nVeuillez mettre en place un nouveau partage.",
+ "gui_server_timeout_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai.\nVeuillez la mettre à jour pour commencer le partage.",
+ "close_on_timeout": "Arrêté, car la minuterie d’arrêt automatique est arrivée au bout de son délai",
+ "gui_add_files": "Ajouter des fichiers",
+ "gui_add_folder": "Ajouter un dossier",
+ "error_cannot_create_data_dir": "Impossible de créer le dossier de données d’OnionShare : {}",
+ "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}",
+ "gui_settings_data_dir_label": "Enregistrer les fichiers dans",
+ "gui_settings_data_dir_browse_button": "Parcourir",
+ "systray_page_loaded_message": "L’adresse OnionShare a été chargée",
+ "systray_share_started_title": "Le partage est commencé",
+ "systray_share_started_message": "L’envoi de fichiers à quelqu’un est commencé",
+ "systray_share_completed_title": "Le partage est terminé",
+ "systray_share_canceled_title": "Le partage a été annulé",
+ "systray_share_canceled_message": "Quelqu’un a annulé la réception de vos fichiers",
+ "systray_receive_started_title": "La réception est commencée",
+ "systray_receive_started_message": "Quelqu’un vous envoie des fichiers",
+ "gui_all_modes_history": "Historique",
+ "gui_all_modes_clear_history": "Tout effacer",
+ "gui_all_modes_transfer_started": "Démarré le {}",
+ "gui_all_modes_transfer_finished_range": "Transféré le {} à {}",
+ "gui_all_modes_transfer_finished": "Transféré le {}",
+ "gui_all_modes_progress_complete": "%p %, {0:s} écoulé.",
+ "gui_all_modes_progress_starting": "{0:s}, %p % (estimation)",
+ "gui_all_modes_progress_eta": "{0:s}, fin prévue : {1:s}, %p %",
+ "gui_share_mode_no_files": "Aucun fichier n’a encore été envoyé",
+ "gui_share_mode_timeout_waiting": "En attente de la fin de l’envoi",
+ "gui_receive_mode_no_files": "Aucun fichier n’a encore été reçu",
+ "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception",
+ "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service onion",
+ "systray_share_completed_message": "L’envoi de fichiers est terminé",
+ "gui_all_modes_transfer_canceled": "Annulé le {}",
+ "gui_settings_onion_label": "Paramètres onion",
+ "gui_all_modes_transfer_canceled_range": "Annulé {} - {}"
}
diff --git a/share/locale/ga.json b/share/locale/ga.json
new file mode 100644
index 00000000..114661d2
--- /dev/null
+++ b/share/locale/ga.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "Seirbhís onion á shocrú ar phort {0:d}.",
+ "preparing_files": "Comhaid á gcomhbhrú.",
+ "give_this_url": "Tabhair an seoladh seo don fhaighteoir:",
+ "give_this_url_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don fhaighteoir:",
+ "give_this_url_receive": "Tabhair an seoladh seo don seoltóir:",
+ "give_this_url_receive_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don seoltóir:",
+ "ctrlc_to_stop": "Brúigh Ctrl+C chun stop a chur leis an bhfreastalaí",
+ "not_a_file": "Ní comhad bailí é {0:s}.",
+ "not_a_readable_file": "Ní comhad inléite é {0:s}.",
+ "no_available_port": "Níorbh fhéidir port a aimsiú chun an tseirbhís onion a thosú",
+ "other_page_loaded": "Seoladh lódáilte",
+ "close_on_timeout": "Cuireadh stop leis toisc go bhfuil an t-amadóir caite",
+ "closing_automatically": "Cuireadh stop leis toisc go bhfuil an íoslódáil críochnaithe",
+ "timeout_download_still_running": "Ag fanacht go gcríochnódh an íoslódáil",
+ "large_filesize": "Rabhadh: D'fhéadfadh go dtógfadh sé tamall fada comhad mór a sheoladh",
+ "systray_menu_exit": "Scoir",
+ "systray_download_started_title": "Tosaíodh Íoslódáil OnionShare",
+ "systray_download_started_message": "Thosaigh úsáideoir ag íoslódáil do chuid comhad",
+ "systray_download_completed_title": "Críochnaíodh Íoslódáil OnionShare",
+ "systray_download_completed_message": "Tá do chuid comhad íoslódáilte ag an úsáideoir",
+ "systray_download_canceled_title": "Cuireadh Íoslódáil OnionShare ar ceal",
+ "systray_download_canceled_message": "Chuir an t-úsáideoir an íoslódáil ar ceal",
+ "systray_upload_started_title": "Tosaíodh Uaslódáil OnionShare",
+ "systray_upload_started_message": "Thosaigh úsáideoir ag uaslódáil comhad go dtí do ríomhaire",
+ "help_local_only": "Ná húsáid Tor (tástáil amháin)",
+ "help_stay_open": "Lean ort ag comhroinnt tar éis an chéad íoslódáil",
+ "help_shutdown_timeout": "Stop ag comhroinnt tar éis líon áirithe soicindí",
+ "help_stealth": "Úsáid údarú cliaint (ardleibhéal)",
+ "help_receive": "Glac le comhaid chomhroinnte in áit iad a sheoladh",
+ "help_debug": "Déan tuairisc ar earráidí OnionShare ar stdout, agus earráidí Gréasáin ar an diosca",
+ "help_filename": "Liosta comhad nó fillteán le comhroinnt",
+ "help_config": "Suíomh saincheaptha don chomhad cumraíochta JSON (roghnach)",
+ "gui_drag_and_drop": "Tarraing agus scaoil comhaid agus fillteáin\nchun iad a chomhroinnt",
+ "gui_add": "Cuir Leis",
+ "gui_delete": "Scrios",
+ "gui_choose_items": "Roghnaigh",
+ "gui_share_start_server": "Comhroinn",
+ "gui_share_stop_server": "Stop ag comhroinnt",
+ "gui_share_stop_server_shutdown_timeout": "Stop ag Comhroinnt ({}s fágtha)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}",
+ "gui_receive_start_server": "Tosaigh an Mód Glactha",
+ "gui_receive_stop_server": "Stop an Mód Glactha",
+ "gui_receive_stop_server_shutdown_timeout": "Stop an Mód Glactha ({}s fágtha)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}",
+ "gui_copy_url": "Cóipeáil an Seoladh",
+ "gui_copy_hidservauth": "Cóipeáil HidServAuth",
+ "gui_downloads": "Stair Íoslódála",
+ "gui_no_downloads": "Níl aon rud íoslódáilte agat fós",
+ "gui_canceled": "Curtha ar ceal",
+ "gui_copied_url_title": "Cóipeáladh an Seoladh OnionShare",
+ "gui_copied_url": "Cóipeáladh an seoladh OnionShare go dtí an ghearrthaisce",
+ "gui_copied_hidservauth_title": "Cóipeáladh HidServAuth",
+ "gui_copied_hidservauth": "Cóipeáladh an líne HidServAuth go dtí an ghearrthaisce",
+ "gui_please_wait": "Ag tosú... Cliceáil lena chur ar ceal.",
+ "gui_download_upload_progress_complete": "%[%, {0:s} caite.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (á áireamh)",
+ "gui_download_upload_progress_eta": "{0:s}, am teachta measta: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Fan soic",
+ "gui_share_quit_warning": "Tá tú le linn roinnt comhad a sheoladh. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?",
+ "gui_receive_quit_warning": "Tá tú le linn roinnt comhad a íoslódáil. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?",
+ "gui_quit_warning_quit": "Scoir",
+ "gui_quit_warning_dont_quit": "Cealaigh",
+ "error_rate_limit": "Rinne duine éigin an iomarca iarrachtaí míchearta ar do sheoladh, agus dá bharr sin stop OnionShare an freastalaí. Tosaigh ag comhroinnt arís agus cuir seoladh nua chuig an bhfaighteoir.",
+ "zip_progress_bar_format": "Á chomhbhrú: %p%",
+ "error_stealth_not_supported": "Chun údarú cliaint a úsáid, teastaíonn uait Tor 0.2.9.1-alpha (nó Brabhsálaí 6.5) agus python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "Teastaíonn uait ar a laghad Tor 0.2.7.1 agus python3-stem 1.4.0 chun OnionShare a úsáid.",
+ "gui_settings_window_title": "Socruithe",
+ "gui_settings_whats_this": "<a href='{0:s}'>Cad é seo</a>",
+ "gui_settings_stealth_option": "Úsáid údarú cliaint (seanleagan)",
+ "gui_settings_stealth_hidservauth_string": "Toisc gur shábháil tú d'eochair phríobháideach, anois is féidir leat\ncliceáil chun an HidServAuth a chóipeáil.",
+ "gui_settings_autoupdate_label": "Lorg nuashonruithe",
+ "gui_settings_autoupdate_option": "Cuir in iúl dom nuair a bheidh leagan nua ar fáil",
+ "gui_settings_autoupdate_timestamp": "Seiceáilte: {}",
+ "gui_settings_autoupdate_timestamp_never": "Níor seiceáladh riamh",
+ "gui_settings_autoupdate_check_button": "Lorg Nuashonrú",
+ "gui_settings_general_label": "Socruithe ginearálta",
+ "gui_settings_sharing_label": "Socruithe comhroinnte",
+ "gui_settings_close_after_first_download_option": "Stop ag comhroinnt tar éis an chéad íoslódáil",
+ "gui_settings_connection_type_label": "Cén chaoi ar chóir do OnionShare ceangal le Tor?",
+ "gui_settings_connection_type_bundled_option": "Úsáid an leagan de Tor ionsuite in OnionShare",
+ "gui_settings_connection_type_automatic_option": "Déan cumraíocht uathoibríoch le Brabhsálaí Tor",
+ "gui_settings_connection_type_control_port_option": "Ceangal trí phort rialaithe",
+ "gui_settings_connection_type_socket_file_option": "Ceangal trí chomhad soicéid",
+ "gui_settings_connection_type_test_button": "Tástáil an Ceangal le Tor",
+ "gui_settings_control_port_label": "Port rialaithe",
+ "gui_settings_socket_file_label": "Comhad soicéid",
+ "gui_settings_socks_label": "Port SOCKS",
+ "gui_settings_authenticate_label": "Socruithe fíordheimhnithe Tor",
+ "gui_settings_authenticate_no_auth_option": "Gan fíordheimhniú, nó fíordheimhniú le fianán",
+ "gui_settings_authenticate_password_option": "Focal faire",
+ "gui_settings_password_label": "Focal faire",
+ "gui_settings_tor_bridges": "Tacaíocht do dhroichid Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Ná húsáid droichid",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4 (obfs4proxy de dhíth)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite(Azure)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite (Azure) (obfs4proxy de dhíth)",
+ "gui_settings_meek_lite_expensive_warning": "Rabhadh: Tá sé an-chostasach ar Thionscadal Tor na droichid meek_lite a chur ar fáil.<br><br>Iarraimid ort gan iad a úsáid má tá tú in ann ceangal díreach a bhunú le Tor, nó trí chóras iompair obfs4, nó trí dhroichead eile.",
+ "gui_settings_tor_bridges_custom_radio_option": "Úsáid droichid shaincheaptha",
+ "gui_settings_tor_bridges_custom_label": "Is féidir leat droichid a fháil ó <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Níl aon cheann de na droichid ag obair.\nSeiceáil arís iad, nó bain triail as droichid eile.",
+ "gui_settings_button_save": "Sábháil",
+ "gui_settings_button_cancel": "Cealaigh",
+ "gui_settings_button_help": "Cabhair",
+ "gui_settings_shutdown_timeout_checkbox": "Úsáid amadóir uathstoptha",
+ "gui_settings_shutdown_timeout": "Stop ag comhroinnt ag:",
+ "settings_error_unknown": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor toisc nach féidir linn ciall a bhaint as na socruithe.",
+ "settings_error_automatic": "Níorbh fhéidir ceangal a bhunú leis an rialaitheoir Tor. An bhfuil Brabhsálaí Tor (ar fáil ó torproject.org) ag rith sa gcúlra?",
+ "settings_error_socket_port": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor ag {}:{}.",
+ "settings_error_socket_file": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor trí chomhad soicéid {}.",
+ "settings_error_auth": "Ceangailte le {}:{}, ach ní féidir an ceangal a fhíordheimhniú. B'fhéidir nach rialaitheoir Tor é seo?",
+ "settings_error_missing_password": "Ceangailte le rialaitheoir Tor, ach teastaíonn focal faire uaidh.",
+ "settings_error_unreadable_cookie_file": "Ceangailte le rialaitheoir Tor, ach seans go bhfuil an focal faire mícheart, nó níl cead ag an úsáideoir an comhad ina bhfuil na fianáin a léamh.",
+ "settings_error_bundled_tor_not_supported": "Ní féidir an leagan de Tor a thagann le OnionShare a úsáid sa mód forbartha ar Windows nó ar macOS.",
+ "settings_error_bundled_tor_timeout": "An iomarca ama ag ceangal le Tor. B'fhéidir nach bhfuil ceangailte leis an Idirlíon, nó nach bhfuil clog do chórais socraithe mar is ceart?",
+ "settings_error_bundled_tor_broken": "Níorbh fhéidir le OnionShare ceangal le Tor sa gcúlra:\n{}",
+ "settings_test_success": "Ceangailte leis an rialaitheoir Tor.\n\nLeagan de Tor: {}\nTacaíonn sé le seirbhísí onion gearrshaolacha: {}.\nTacaíonn sé le fíordheimhniú cliaint: {}.\nTacaíonn sé le seoltaí .onion den chéad ghlúin eile: {}.",
+ "error_tor_protocol_error": "Tharla earráid le Tor: {}",
+ "error_tor_protocol_error_unknown": "Tharla earráid anaithnid le Tor",
+ "error_invalid_private_key": "Ní thacaítear le heochair phríobháideach den sórt seo",
+ "connecting_to_tor": "Ag ceangal le líonra Tor",
+ "update_available": "Leagan nua de OnionShare ar fáil. <a href='{}'>Cliceáil anseo</a> lena íoslódáil.<br><br>Tá {} agat agus is é {} an leagan is déanaí.",
+ "update_error_check_error": "Theip orainn nuashonruithe a lorg: Deir suíomh Gréasáin OnionShare gurb é '{}' an leagan is déanaí, leagan nach n-aithnímid…",
+ "update_error_invalid_latest_version": "Theip orainn nuashonruithe a lorg: B'fhéidir nach bhfuil ceangailte le Tor, nó nach bhfuil suíomh OnionShare ag obair faoi láthair?",
+ "update_not_available": "Tá an leagan is déanaí de OnionShare agat cheana.",
+ "gui_tor_connection_ask": "An bhfuil fonn ort na socruithe líonra a oscailt chun an fhadhb a réiteach?",
+ "gui_tor_connection_ask_open_settings": "Tá",
+ "gui_tor_connection_ask_quit": "Scoir",
+ "gui_tor_connection_error_settings": "Bain triail as na socruithe líonra a athrú chun ceangal le líonra Tor ó OnionShare.",
+ "gui_tor_connection_canceled": "Níorbh fhéidir ceangal a bhunú le Tor.\n\nDeimhnigh go bhfuil tú ceangailte leis an Idirlíon, ansin oscail OnionShare arís agus socraigh an ceangal le Tor.",
+ "gui_tor_connection_lost": "Dícheangailte ó Tor.",
+ "gui_server_started_after_timeout": "Bhí an t-amadóir uathstoptha caite sular thosaigh an freastalaí.\nCaithfidh tú comhroinnt nua a chruthú.",
+ "gui_server_timeout_expired": "Tá an t-amadóir uathstoptha caite cheana.\nCaithfidh tú é a athshocrú sular féidir leat comhaid a chomhroinnt.",
+ "share_via_onionshare": "Comhroinn trí OnionShare é",
+ "gui_use_legacy_v2_onions_checkbox": "Úsáid seoltaí sean-nóis",
+ "gui_save_private_key_checkbox": "Úsáid seoladh seasmhach (seanleagan)",
+ "gui_share_url_description": "Tá <b>aon duine</b> a bhfuil an seoladh OnionShare aige/aici in ann do chuid comhad a <b>íoslódáil</b> le <b>Brabhsálaí Tor</b>: <img src='{}' />",
+ "gui_receive_url_description": "Tá <b>aon duine</b> a bhfuil an seoladh OnionShare aige/aici in ann comhaid a <b>uaslódáil</b> go dtí do ríomhaire le <b>Brabhsálaí Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.<br><br>Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)",
+ "gui_url_label_stay_open": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.",
+ "gui_url_label_onetime": "Stopfaidh an chomhroinnt seo nuair a chríochnóidh sé den chéad uair.",
+ "gui_url_label_onetime_and_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.<br><br>Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)",
+ "gui_status_indicator_share_stopped": "Réidh le comhroinnt",
+ "gui_status_indicator_share_working": "Á thosú…",
+ "gui_status_indicator_share_started": "Comhroinnt",
+ "gui_status_indicator_receive_stopped": "Réidh le glacadh le comhaid",
+ "gui_status_indicator_receive_working": "Á thosú…",
+ "gui_status_indicator_receive_started": "Glacadh",
+ "gui_file_info": "{} comhad, {}",
+ "gui_file_info_single": "{} chomhad, {}",
+ "history_in_progress_tooltip": "{} ar siúl",
+ "history_completed_tooltip": "{} críochnaithe",
+ "info_in_progress_uploads_tooltip": "{} uaslódáil ar siúl faoi láthair",
+ "info_completed_uploads_tooltip": "{} uaslódáil críochnaithe",
+ "error_cannot_create_downloads_dir": "Níorbh fhéidir fillteán a chruthú do chomhaid a nglacann tú leo: {}",
+ "receive_mode_downloads_dir": "Cuirfear comhaid a sheoltar chugat san fhillteán seo: {}",
+ "receive_mode_warning": "Rabhadh: Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire, fiú comhaid chontúirteacha a dhéanfadh dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.",
+ "gui_receive_mode_warning": "Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire.<br><br><b>Tá comhaid áirithe an-chontúirteach agus dhéanfaidís dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.</b>",
+ "receive_mode_upload_starting": "Uaslódáil, méid iomlán {}, á tosú",
+ "receive_mode_received_file": "Faighte: {}",
+ "gui_mode_share_button": "Comhroinn Comhaid",
+ "gui_mode_receive_button": "Glac le Comhaid",
+ "gui_settings_receiving_label": "Socruithe glactha",
+ "gui_settings_downloads_label": "Sábháil comhaid i",
+ "gui_settings_downloads_button": "Brabhsáil",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Tá cead ag an seoltóir stop a chur leis an mód glactha",
+ "gui_settings_public_mode_checkbox": "Mód poiblí",
+ "systray_close_server_title": "Tá an freastalaí OnionShare dúnta",
+ "systray_close_server_message": "Dhún úsáideoir an freastalaí",
+ "systray_page_loaded_title": "Lódáladh leathanach OnionShare",
+ "systray_download_page_loaded_message": "Lódáil úsáideoir an leathanach íoslódála",
+ "systray_upload_page_loaded_message": "Lódáil úsáideoir an leathanach uaslódála",
+ "gui_uploads": "Stair Uaslódála",
+ "gui_no_uploads": "Níl aon rud uaslódáilte agat fós",
+ "gui_clear_history": "Glan Uile",
+ "gui_upload_in_progress": "Tosaíodh an Uaslódáil {}",
+ "gui_upload_finished_range": "Uaslódáladh {} go {}",
+ "gui_upload_finished": "Uaslódáladh {}",
+ "gui_download_in_progress": "Tosaíodh an Íoslódáil {}",
+ "gui_open_folder_error_nautilus": "Ní féidir an fillteán a oscailt toisc nach bhfuil nautilus ar fáil. Tá an comhad anseo: {}",
+ "gui_settings_language_label": "Do rogha teanga",
+ "gui_settings_language_changed_notice": "Atosaigh OnionShare chun an teanga nua a chur i bhfeidhm."
+}
diff --git a/share/locale/gu.json b/share/locale/gu.json
new file mode 100644
index 00000000..8ebfc5fb
--- /dev/null
+++ b/share/locale/gu.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "ક્યારેય નહીં",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/he.json b/share/locale/he.json
new file mode 100644
index 00000000..11c255f6
--- /dev/null
+++ b/share/locale/he.json
@@ -0,0 +1,188 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_add_files": "",
+ "gui_add_folder": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_connect_to_tor_for_onion_settings": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/hu.json b/share/locale/hu.json
new file mode 100644
index 00000000..34cc480b
--- /dev/null
+++ b/share/locale/hu.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Kilépés",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "Delete",
+ "gui_choose_items": "Kiválaszt",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Kilépés",
+ "gui_quit_warning_dont_quit": "Megszakítás",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "Beállítások",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Soha",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Általános beállítások",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "Kontroll port",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "Password",
+ "gui_settings_password_label": "Password",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "Mentés",
+ "gui_settings_button_cancel": "Megszakítás",
+ "gui_settings_button_help": "Súgó",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Igen",
+ "gui_tor_connection_ask_quit": "Kilépés",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "Megosztás",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "Bevétel",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Tallózás",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "Az összes törlése",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "Előnyben részesített nyelv",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/id.json b/share/locale/id.json
new file mode 100644
index 00000000..0557aca9
--- /dev/null
+++ b/share/locale/id.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Keluar",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "Tambahkan",
+ "gui_delete": "Hapus",
+ "gui_choose_items": "Pilih",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "Dibatalkan",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Keluar",
+ "gui_quit_warning_dont_quit": "Batal",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "Pengaturan",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Tak pernah",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Pengaturan umum",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "Port kontrol",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "Simpan",
+ "gui_settings_button_cancel": "Batal",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Ya",
+ "gui_tor_connection_ask_quit": "Keluar",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Jelajahi",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/is.json b/share/locale/is.json
new file mode 100644
index 00000000..85b492ab
--- /dev/null
+++ b/share/locale/is.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Hætta",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Hætta",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Aldrei",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Almennar stillingar",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Já",
+ "gui_tor_connection_ask_quit": "Hætta",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Flakka",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/it.json b/share/locale/it.json
index 75532c34..6e23cb1e 100644
--- a/share/locale/it.json
+++ b/share/locale/it.json
@@ -1,43 +1,216 @@
{
- "connecting_ctrlport": "Connessione alla porta di controllo di Tor per inizializzare il servizio nascosto sulla porta {0:d}.",
- "cant_connect_ctrlport": "Impossibile connettere alla porta di controllo di Tor sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.",
- "cant_connect_socksport": "Impossibile connettersi al server Tor SOCKS5 sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.",
- "preparing_files": "Preparazione dei files da condividere.",
- "wait_for_hs": "In attesa che l'HS sia pronto:",
- "wait_for_hs_trying": "Tentativo...",
- "wait_for_hs_nope": "Non è ancora pronto.",
- "wait_for_hs_yup": "Pronto!",
- "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:",
- "ctrlc_to_stop": "Premi Ctrl-C per fermare il server",
- "not_a_file": "{0:s} non è un file.",
- "download_page_loaded": "Pagina di Download caricata",
+ "preparing_files": "Compressione dei file in corso.",
+ "give_this_url": "Dai questa URL al destinatario:",
+ "ctrlc_to_stop": "Premi Ctrl+C per fermare il server",
+ "not_a_file": "{0:s} non è un file valido.",
"other_page_loaded": "URL caricato",
- "closing_automatically": "Chiusura automatica dopo aver finito il download",
- "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore",
- "error_tails_invalid_port": "Valore non valido, la porta deve essere un numero intero",
- "error_tails_unknown_root": "Errore sconosciuto con il processo Tails root",
- "help_tails_port": "Solo per Tails: porta per passare il firewall, avvio del servizio nascosto",
- "help_local_only": "Non usare tor: è solo per lo sviluppo",
- "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download",
- "help_transparent_torification": "Il mio sistema usa tor in modo trasparente",
+ "closing_automatically": "Fermato perchè il trasferimento è stato completato",
+ "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore",
+ "help_local_only": "Non usare Tor (solo per lo sviluppo)",
+ "help_stay_open": "Mantieni la condivisione attiva anche dopo che i file sono stati inviati",
"help_debug": "Registra gli errori sul disco",
"help_filename": "Lista dei file o cartelle da condividere",
- "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra",
+ "gui_drag_and_drop": "Trascina e rilascia i file e le cartelle per iniziare la condivisione",
"gui_add": "Aggiungi",
"gui_delete": "Cancella",
"gui_choose_items": "Scegli",
- "gui_start_server": "Inizia la condivisione",
- "gui_stop_server": "Ferma la condivisione",
- "gui_copy_url": "Copia lo URL",
- "gui_downloads": "Downloads:",
- "gui_canceled": "Cancellati",
- "gui_copied_url": "URL Copiato nella clipboard",
- "gui_starting_server1": "Avviamento del servizio nascosto Tor...",
- "gui_starting_server2": "Elaborazione files...",
- "gui_starting_server3": "In attesa del servizio nascosto Tor...",
- "gui_please_wait": "Attendere prego...",
- "error_hs_dir_cannot_create": "Impossibile create la cartella per il servizio nascosto {0:s}",
- "error_hs_dir_not_writable": "La cartella per il servizio nascosto {0:s} non ha i permessi di scrittura",
- "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione",
- "zip_progress_bar_format": "Elaborazione files: %p%"
+ "gui_share_start_server": "Inizia la condivisione",
+ "gui_share_stop_server": "Arresta la condivisione",
+ "gui_copy_url": "Copia la URL",
+ "gui_downloads": "Cronologia dei Download",
+ "gui_canceled": "Annullato",
+ "gui_copied_url": "URL Copiato negli appunti",
+ "gui_please_wait": "Avviato... Cliccare per interrompere.",
+ "zip_progress_bar_format": "Compressione in corso: %p%",
+ "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.",
+ "give_this_url_stealth": "Dai questa URL e la linea HidServAuth al destinatario:",
+ "give_this_url_receive": "Dai questo indirizzo al mittente:",
+ "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth con il mittente:",
+ "not_a_readable_file": "{0:s} non è un file leggibile.",
+ "no_available_port": "Non è stato possibile trovare alcuna porta per avviare il servizio onion",
+ "close_on_timeout": "Arrestato per tempo scaduto",
+ "timeout_download_still_running": "download in corso, attendere",
+ "systray_menu_exit": "Termina",
+ "systray_download_started_title": "Download con OnionShare avviato",
+ "systray_download_started_message": "Un utente ha iniziato il download dei tuoi file",
+ "systray_download_completed_title": "Download completato",
+ "systray_download_completed_message": "L'utente ha terminato il download dei tuoi file",
+ "systray_download_canceled_title": "OnionShare Download cancellato",
+ "systray_download_canceled_message": "L'utente ha interrotto il download",
+ "systray_upload_started_title": "Upload con OnionShare avviato",
+ "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer",
+ "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi",
+ "help_stealth": "Usa l'autorizzazione del client (avanzato)",
+ "help_config": "Specifica il percorso del file di configurazione del JSON personalizzato",
+ "gui_share_stop_server_shutdown_timeout": "Arresta la condivisione ({}s rimanenti)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Il timer si arresterà tra {}",
+ "gui_receive_start_server": "Inizia la ricezione",
+ "gui_receive_stop_server": "Arresta la ricezione",
+ "gui_receive_stop_server_shutdown_timeout": "Interrompi la ricezione ({}s rimanenti)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Il timer termina tra {}",
+ "gui_copy_hidservauth": "Copia HidServAuth",
+ "gui_no_downloads": "Ancora nessun Download",
+ "gui_copied_url_title": "Indirizzo OnionShare copiato",
+ "gui_copied_hidservauth_title": "HidServAuth copiato",
+ "gui_copied_hidservauth": "HidServAuth copiato negli appunti",
+ "gui_download_upload_progress_complete": "%p%, {0:s} trascorsi.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (calcolato)",
+ "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org",
+ "gui_quit_title": "Non così in fretta",
+ "gui_share_quit_warning": "Stai per inviare dei file. Sei sicuro di voler uscire da OnionShare?",
+ "gui_receive_quit_warning": "Stai per ricevere dei file, vuoi davvero terminare e chiudere OnionShare?",
+ "gui_quit_warning_quit": "Esci",
+ "gui_quit_warning_dont_quit": "Cancella",
+ "error_rate_limit": "Qualcuno ha tentato troppe volte di accedere al tuo indirizzo, questo potrebbe comprometterne la sicurezza quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL.",
+ "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno della versione di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.",
+ "gui_settings_window_title": "Impostazioni",
+ "gui_settings_whats_this": "<a href='{0:s}'>Cos'è questo?</a>",
+ "help_receive": "Ricevi le condivisioni invece di inviarle",
+ "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)",
+ "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi cliccare per copiare il tuo HidServAuth.",
+ "gui_settings_autoupdate_label": "Controlla se vi sono nuove versioni",
+ "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione",
+ "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}",
+ "gui_settings_autoupdate_timestamp_never": "Mai",
+ "gui_settings_autoupdate_check_button": "Controlla per una nuova versione",
+ "gui_settings_general_label": "Impostazioni generali",
+ "gui_settings_sharing_label": "Sto condividendo le impostazioni",
+ "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo che i file sono stati inviati",
+ "gui_settings_connection_type_label": "Come si dovrebbe connettere OnionShare a Tor?",
+ "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare",
+ "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser",
+ "gui_settings_language_label": "Lingua preferita",
+ "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.",
+ "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati",
+ "timeout_upload_still_running": "In attesa del completamento dell'upload",
+ "gui_add_files": "Aggiungi archivi",
+ "gui_add_folder": "Aggiungi una cartella",
+ "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo",
+ "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket",
+ "gui_settings_connection_type_test_button": "Prova la connessione Tor",
+ "gui_settings_socket_file_label": "File di socket",
+ "gui_settings_socks_label": "Porta SOCKS",
+ "gui_settings_authenticate_label": "Impostazioni di autenticazione Tor",
+ "gui_settings_authenticate_password_option": "Password",
+ "gui_settings_password_label": "Password",
+ "gui_settings_control_port_label": "Porta di controllo",
+ "gui_settings_authenticate_no_auth_option": "Nessuna autenticazione o autenticazione tramite cookie",
+ "gui_settings_tor_bridges": "Supporto bridge Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Non usare i bridge",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Usare i trasporti obfs4 integrati selezionabili",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usare i trasporti obfs4 integrati selezionabili (richiede obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usare i trasporti integrati meek_lite (Azure) selezionabili",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usare i trasporti integrati meek_lite (Azure) selezionabili (richiede obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Attenzione: i bridge meek_lite sono molto pesanti per l'esecuzione del progetto Tor.<br><br>Da usare solo se impossibile connettersi a Tor direttamente, con obfs4, o altri bridge normali.",
+ "gui_settings_tor_bridges_custom_label": "Puoi prendere i bridge da <a href=\"https://bridges.torproject.org/options\">1 https://bridges.torproject.org</a>2",
+ "gui_settings_tor_bridges_invalid": "Nessuno dei bridge che hai aggiunto funzionano\nControllali nuovamente o aggiungine altri.",
+ "gui_settings_button_save": "Salva",
+ "gui_settings_button_cancel": "Cancella",
+ "gui_settings_button_help": "Aiuto",
+ "gui_settings_shutdown_timeout_checkbox": "Utilizza il timer di arresto automatico",
+ "gui_settings_shutdown_timeout": "Ferma la condivisione alle:",
+ "settings_error_unknown": "Impossibile connettersi al controller Tor perché le tue impostazioni non hanno senso.",
+ "settings_error_automatic": "Impossibile connettersi al controller Tor. Tor Browser (disponibile da torproject.org) è in esecuzione in background?",
+ "settings_error_socket_port": "Impossibile connettersi al controller Tor in {}: {}.",
+ "settings_error_socket_file": "Impossibile connettersi al controller Tor utilizzando il file socket {}.",
+ "settings_error_auth": "Connesso a {}: {}, ma non può autenticarsi. Forse questo non è un controller Tor?",
+ "settings_error_missing_password": "Collegato al controller Tor, ma richiede una password per l'autenticazione.",
+ "settings_error_unreadable_cookie_file": "Collegato al controller Tor, ma la password potrebbe essere errata o l'utente non è autorizzato a leggere il file cookie.",
+ "settings_error_bundled_tor_not_supported": "L'uso della versione Tor fornita con OnionShare non funziona in modalità sviluppatore su Windows o macOS.",
+ "settings_error_bundled_tor_timeout": "Prendendo troppo tempo per connettersi a Tor. Forse non sei connesso a Internet o hai un orologio di sistema impreciso?",
+ "settings_error_bundled_tor_broken": "OnionShare non è riuscito a connettersi a Tor in background:\n{}",
+ "settings_test_success": "Collegato al controller Tor.\n\nVersione Tor: {}\nSupporta servizi onion effimeri: {}.\nSupporta l'autenticazione del client: {}.\nSupporta indirizzi .onion next-gen: {}.",
+ "error_tor_protocol_error": "Si è verificato un errore con Tor: {}",
+ "error_tor_protocol_error_unknown": "Si è verificato un errore sconosciuto con Tor",
+ "error_invalid_private_key": "Questo tipo di chiave privata non è supportato",
+ "connecting_to_tor": "In connessione alla rete Tor",
+ "update_available": "E' disponibile una nuova versione di OnionShare. <a href='{}'>Clicca qui</a> per scaricarla.<br><br>Stai usando {} e l'ultima versione è {}.",
+ "update_error_check_error": "Non è possibile verificare per nuove versioni: il sito OnionShare dice che l'ultima versione non è riconoscibile '{}'…",
+ "update_error_invalid_latest_version": "Non è possibile controllare per una nuova versione: Magari non sei connesso a Tor, o il sito OnionShare non funziona?",
+ "update_not_available": "Stai usando la ultima versione di OnionShare.",
+ "gui_tor_connection_ask": "Apri le impostazione per trovare la connessione a Tor?",
+ "gui_tor_connection_ask_open_settings": "Sì",
+ "gui_tor_connection_ask_quit": "Esci",
+ "gui_tor_connection_error_settings": "Prova a modificare le impostazioni di come OnionShare si connette alla rete Tor.",
+ "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nVerifica la connessione a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.",
+ "gui_tor_connection_lost": "Disconnesso da Tor.",
+ "gui_server_started_after_timeout": "Il timer auto-stop si è esaurito prima dell'avvio del server.\nSi prega di fare una nuova condivisione.",
+ "gui_server_timeout_expired": "Il timer auto-stop ha già finito.\nPer favore aggiornalo per iniziare la condivisione.",
+ "share_via_onionshare": "Usa OnionShare",
+ "gui_connect_to_tor_for_onion_settings": "Connetti a Tor per vedere le impostazioni del servizio onion",
+ "gui_use_legacy_v2_onions_checkbox": "Usa gli indirizzi legacy",
+ "gui_save_private_key_checkbox": "Usa un indirizzo persistente",
+ "gui_share_url_description": "<b>1 Tutti</b>2 con questo l'indirizzo di OnionShare possono <b>3 scaricare</b>4 i tuoi file usando <b>5 il Browser Tor</b>6: <img src='{}' />7",
+ "gui_receive_url_description": "<b>1 Tutti</b>2 con questo indirizzo OnionShare possono <b>3 caricare</b>4 file nel tuo computer usando <b>5 Tor Browser</b>6: <img src='{}' />7",
+ "gui_url_label_persistent": "Questa condivisione non si arresterà automaticamente. <br> <br> Ogni successiva condivisione riutilizza l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)",
+ "gui_url_label_stay_open": "Questa condivisione non si arresterà automaticamente.",
+ "gui_url_label_onetime": "Questa condivisione verrà interrotta dopo il primo completamento.",
+ "gui_url_label_onetime_and_persistent": "Questa condivisione non si arresterà automaticamente. <br> <br> Ogni condivisione successiva riutilizzerà l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)",
+ "gui_status_indicator_share_stopped": "Pronto per condividere",
+ "gui_status_indicator_share_working": "Iniziando…",
+ "gui_status_indicator_share_started": "Condividendo",
+ "gui_status_indicator_receive_stopped": "Pronto per ricevere",
+ "gui_status_indicator_receive_working": "Iniziando…",
+ "gui_status_indicator_receive_started": "Ricevendo",
+ "gui_file_info": "{} file, {}",
+ "gui_file_info_single": "{} file, {}",
+ "history_in_progress_tooltip": "{} in avanzamento",
+ "history_completed_tooltip": "{} completato",
+ "info_in_progress_uploads_tooltip": "{} upload(s) in avanzamento",
+ "info_completed_uploads_tooltip": "{} upload(s) completati",
+ "error_cannot_create_downloads_dir": "Non è stato possibile creare la cartella in modalità ricezione: {}",
+ "receive_mode_downloads_dir": "I file a te mandati appariranno in questa cartella: {}",
+ "receive_mode_warning": "Attenzione: La modalità ricezione permette alla gente di fare l'upload di file nel tuo computer. Alcuni file possono potenzialmente prendere il controllo del tuo computer se aperti. Apri solamente file inviati da persone di cui ti fidi, o se sai quello che stai facendo.",
+ "gui_receive_mode_warning": "La modalità ricezione permette alle persone di fare l'upload di file nel tuo computer.<br><br><b>Alcuni file possono potenzialmente prendere il controllo del tuo computer se li apri. Apri solamente file di persone di cui ti fidi, o se sai quello che stai facendo.</b>",
+ "receive_mode_upload_starting": "Upload di dimensione totale {} sta partendo",
+ "receive_mode_received_file": "Ricevuto: {}",
+ "gui_mode_share_button": "Condividi File",
+ "gui_mode_receive_button": "Ricevi File",
+ "gui_settings_receiving_label": "Impostazioni di Ricezione",
+ "gui_settings_downloads_label": "Salva i file in",
+ "gui_settings_downloads_button": "Navigare",
+ "gui_settings_public_mode_checkbox": "Modalità pubblica",
+ "systray_close_server_title": "Il server OnionShare è inattivo",
+ "systray_close_server_message": "Un utente ha disattivato il Server",
+ "systray_page_loaded_title": "Pagina caricata",
+ "systray_download_page_loaded_message": "Un utente ha caricato la pagina di Download",
+ "systray_upload_page_loaded_message": "Un utente ha caricato la pagina di Upload",
+ "gui_uploads": "Storia degli Upload",
+ "gui_no_uploads": "Nessun Upload ancora",
+ "gui_clear_history": "Pulisci tutto",
+ "gui_upload_in_progress": "Upload iniziato {}",
+ "gui_upload_finished_range": "Upload eseguito {} a {}",
+ "gui_upload_finished": "Caricato {}",
+ "gui_download_in_progress": "Download iniziato {}",
+ "gui_open_folder_error_nautilus": "Impossibile aprire la cartella perché Nautilus non è disponibile. Il file è qui: {}",
+ "gui_settings_onion_label": "Impostazioni Onion",
+ "error_cannot_create_data_dir": "Non è possibile creare la cartella dati OnionShare: {}",
+ "receive_mode_data_dir": "I file inviati a te appariranno in questa cartella: {}",
+ "gui_settings_data_dir_label": "Salva i file in",
+ "gui_settings_data_dir_browse_button": "Naviga",
+ "systray_page_loaded_message": "Indirizzo OnionShare caricato",
+ "systray_share_started_title": "Condivisione iniziata",
+ "systray_share_started_message": "Inizio dell'invio dei file a qualcuno",
+ "systray_share_completed_title": "Condivisione completata",
+ "systray_share_completed_message": "Completato l'invio dei file",
+ "systray_share_canceled_title": "Condivisione annullata",
+ "systray_share_canceled_message": "Qualcuno ha annullato la ricezione dei file",
+ "systray_receive_started_title": "Inizio ricezione",
+ "systray_receive_started_message": "Qualcuno ti sta inviando dei file",
+ "gui_all_modes_history": "Storico",
+ "gui_all_modes_clear_history": "Pulisci tutto",
+ "gui_all_modes_transfer_started": "Iniziato {}",
+ "gui_all_modes_transfer_finished_range": "Trasferito {} - {}",
+ "gui_all_modes_transfer_finished": "Trasferito {}",
+ "gui_all_modes_transfer_canceled_range": "Annullato {} - {}",
+ "gui_all_modes_transfer_canceled": "Annullato {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} trascorsi.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (in calcolo)",
+ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_share_mode_no_files": "Nessun file ancora inviato",
+ "gui_share_mode_timeout_waiting": "In attesa di finire l'invio",
+ "gui_receive_mode_no_files": "Nessun file ricevuto ancora",
+ "gui_receive_mode_timeout_waiting": "In attesa di finire la ricezione"
}
diff --git a/share/locale/ja.json b/share/locale/ja.json
new file mode 100644
index 00000000..ad559ca2
--- /dev/null
+++ b/share/locale/ja.json
@@ -0,0 +1,216 @@
+{
+ "config_onion_service": "{0:d}番ポートを使ってonionサービス設定中...",
+ "preparing_files": "ファイル圧縮中...",
+ "give_this_url": "このアドレスを受領者と共有して下さい。",
+ "give_this_url_stealth": "このアドレスとHidServAuth行を受領者と共有して下さい。",
+ "give_this_url_receive": "このアドレスを送信者と共有して下さい。",
+ "give_this_url_receive_stealth": "このアドレスとHidServAuth行を送信者と共有して下さい。",
+ "ctrlc_to_stop": "Ctrl+Cキーでサーバーをシャットダウンする",
+ "not_a_file": "{0:s}は有効なファイルではありません。",
+ "not_a_readable_file": "{0:s}は読めるファイルではありません。",
+ "no_available_port": "onionサービスを実行するための利用可能ポートを見つかりません",
+ "other_page_loaded": "アドレスはロードされています",
+ "close_on_timeout": "自動タイマーがタイムアウトしたため停止されました",
+ "closing_automatically": "転送が完了されたため停止されました",
+ "timeout_download_still_running": "ダウンロード完了待ち",
+ "timeout_upload_still_running": "アップロード完了待ち",
+ "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない",
+ "systray_menu_exit": "終了",
+ "systray_download_started_title": "OnionShareダウンロードは開始されました",
+ "systray_download_started_message": "ユーザーがダウンロードを開始しました",
+ "systray_download_completed_title": "OnionShareダウンロード完了",
+ "systray_download_completed_message": "ユーザーがダウンロードし終えました",
+ "systray_download_canceled_title": "OnionShareダウンロードは中止されました",
+ "systray_download_canceled_message": "ユーザーがダウンロードを中止しました",
+ "systray_upload_started_title": "OnionShareアップロードは開始されました",
+ "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました",
+ "help_local_only": "Torを使わない(開発利用のみ)",
+ "help_stay_open": "ファイルが送信された後に共有し続けます",
+ "help_shutdown_timeout": "数秒後に共有が停止されます",
+ "help_stealth": "クライアント認証を使う(上級者向け)",
+ "help_receive": "共有を送信する代わりに受信する",
+ "help_debug": "OnionShareのエラーを標準出力に、Webのエラーをディスクに記録する",
+ "help_filename": "共有するファイルとフォルダの一覧",
+ "help_config": "カスタムJSON設定ファイルの位置(任意)",
+ "gui_drag_and_drop": "共有を始めるにはファイルやフォルダをドラッグアンドドロップしてください",
+ "gui_add": "追加",
+ "gui_add_files": "ファイルを追加",
+ "gui_add_folder": "フォルダを追加",
+ "gui_delete": "削除",
+ "gui_choose_items": "選択",
+ "gui_share_start_server": "共有を開始する",
+ "gui_share_stop_server": "共有を停止する",
+ "gui_share_stop_server_shutdown_timeout": "共有を停止中です(残り{}秒)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "{}に自動停止します",
+ "gui_receive_start_server": "受信モードを開始",
+ "gui_receive_stop_server": "受信モードを停止",
+ "gui_receive_stop_server_shutdown_timeout": "受信モードを停止中(残り{}秒)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "{}に自動停止します",
+ "gui_copy_url": "アドレスをコピー",
+ "gui_copy_hidservauth": "HidServAuthをコピー",
+ "gui_downloads": "ダウンロード履歴",
+ "gui_no_downloads": "まだダウンロードがありません",
+ "gui_canceled": "キャンセルされました",
+ "gui_copied_url_title": "OnionShareのアドレスをコピーしました",
+ "gui_copied_url": "OnionShareのアドレスをクリップボードへコピーしました",
+ "gui_copied_hidservauth_title": "HidServAuthをコピーしました",
+ "gui_copied_hidservauth": "HidServAuthの行をクリップボードへコピーしました",
+ "gui_please_wait": "実行中… クリックでキャンセルします。",
+ "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)",
+ "gui_download_upload_progress_eta": "{0:s} 終了予定:{1:s}、%p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "そんなに速くない",
+ "gui_share_quit_warning": "ファイルを送信中です。本当にOnionShareを終了しますか?",
+ "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?",
+ "gui_quit_warning_quit": "終了",
+ "gui_quit_warning_dont_quit": "キャンセル",
+ "error_rate_limit": "誰かが何度も間違えたアドレスをアクセスして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスに送って下さい。",
+ "zip_progress_bar_format": "圧縮中: %p%",
+ "error_stealth_not_supported": "クライアント認証を使用するのに、少なくともTor 0.2.9.1-alpha (それともTor Browser 6.5)とpython3-stem 1.5.0が必要です。",
+ "error_ephemeral_not_supported": "OnionShareは少なくともTor 0.2.7.1とpython3-stem 1.4.0が必要です。",
+ "gui_settings_window_title": "設定",
+ "gui_settings_whats_this": "<a href='{0:s}'>これは何ですか?</a>",
+ "gui_settings_stealth_option": "クライアント認証を使用",
+ "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。",
+ "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする",
+ "gui_settings_autoupdate_option": "更新通知を起動します",
+ "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}",
+ "gui_settings_autoupdate_timestamp_never": "したことがない",
+ "gui_settings_autoupdate_check_button": "更新をチェックする",
+ "gui_settings_general_label": "一般的設定",
+ "gui_settings_sharing_label": "共有設定",
+ "gui_settings_close_after_first_download_option": "ファイルが送信された後に停止する",
+ "gui_settings_connection_type_label": "OnionShareがどうやってTorと接続して欲しい?",
+ "gui_settings_connection_type_bundled_option": "OnionShareに組み込まれるTorバージョンを使用する",
+ "gui_settings_connection_type_automatic_option": "Torブラウザと自動設定してみる",
+ "gui_settings_connection_type_control_port_option": "コントロールポートを使用して接続する",
+ "gui_settings_connection_type_socket_file_option": "ソケットファイルを使用して接続する",
+ "gui_settings_connection_type_test_button": "Torへの接続をテストする",
+ "gui_settings_control_port_label": "コントロールポート",
+ "gui_settings_socket_file_label": "ソケットファイル",
+ "gui_settings_socks_label": "SOCKSポート",
+ "gui_settings_authenticate_label": "Tor認証の設定",
+ "gui_settings_authenticate_no_auth_option": "認証なし、それともクッキー認証",
+ "gui_settings_authenticate_password_option": "パスワード",
+ "gui_settings_password_label": "パスワード",
+ "gui_settings_tor_bridges": "Torブリッジサポート",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "ブリッジを使用しない",
+ "gui_settings_tor_bridges_obfs4_radio_option": "組み込みのobs4 pluggable transportを使用する",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "組み込みのobs4 pluggable transportを使用する(obsf4proxy必要)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "組み込みのmeek_lite (Azure) pluggable transportを使用する",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "組み込みのmeek_lite (Azure) pluggable transportを使用する(obsf4proxy必要)",
+ "gui_settings_meek_lite_expensive_warning": "警告:meek_liteブリッジはTor Projectにとって維持費がかさむ<br><br>直接にTorと接続できない場合、あるいはobsf4ブリッジや他のブリッジが使用できない場合のみに使って下さい。",
+ "gui_settings_tor_bridges_custom_radio_option": "カスタムブリッジを使用する",
+ "gui_settings_tor_bridges_custom_label": "<a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>からブリッジを入手できます",
+ "gui_settings_tor_bridges_invalid": "全ての追加したブリッジは機能しませんでした。\n再確認して、あるいは他のを追加して下さい。",
+ "gui_settings_button_save": "保存",
+ "gui_settings_button_cancel": "キャンセル",
+ "gui_settings_button_help": "ヘルプ",
+ "gui_settings_shutdown_timeout_checkbox": "自動停止タイマーを使用する",
+ "gui_settings_shutdown_timeout": "共有を停止する時間:",
+ "settings_error_unknown": "設定を解釈できないため、Torコントローラーと接続できません。",
+ "settings_error_automatic": "Torコントローラーと接続できません。Torブラウザ(torproject.orgから入手できる)がバックグラウンドで動作していますか?",
+ "settings_error_socket_port": "{}:{}でTorコントローラーと接続できません。",
+ "settings_error_socket_file": "ソケットファイル{}を使用してTorコントローラーと接続できません。",
+ "settings_error_auth": "{}:{}と接続できましたが、認証ができません。これは実際にTorコントローラーですか?",
+ "settings_error_missing_password": "Torコントローラーと接続できましたが、認証にはパスワードが必要です。",
+ "settings_error_unreadable_cookie_file": "Torコントローラーと接続できましたが、パスワードが診違っているあるいはクッキーファイルの読み出し許可がないかもしれない。",
+ "settings_error_bundled_tor_not_supported": "OnionShareに組み込まれているTorバージョンはWindowsやmacOSの開発者モードで動作できません。",
+ "settings_error_bundled_tor_timeout": "Torとの接続は時間がかかり過ぎます。インターネットとの接続、あるいはシステム・クロックの精度には問題がありますか?",
+ "settings_error_bundled_tor_broken": "OnionShareはバックグラウンドで動作しているTorと接続できませんでした:\n{}",
+ "settings_test_success": "Torコントローラーと接続完了。\n\nTorバージョン:{}\nエフェメラルonionサービスをサポートする:{}\nクライアント認証をサポートする:{}\nnext-gen .onionアドレスをサポートする:{}.",
+ "error_tor_protocol_error": "Torとのエラーが生じました: {}",
+ "error_tor_protocol_error_unknown": "Torとの未知のエラーが生じました",
+ "error_invalid_private_key": "この秘密鍵形式は未対応である",
+ "connecting_to_tor": "Torネットワークと接続中",
+ "update_available": "OnionShareの新バージョンはリリースされました。<a href='{}'>こちら</a>から入手できます。<br><br>現行バージョンは{}そして最新バージョンは{}。",
+ "update_error_check_error": "新バージョンのチェックをできなかった:OnionShare公式サイトによれば、最新バージョンは認識できない '{}'です…",
+ "update_error_invalid_latest_version": "新バージョンのチェックをできなかった:多分Torと接続していない、あるいはOnionShare公式サイトはダウンかもしれない?",
+ "update_not_available": "OnionShareの最新バージョンを使っています。",
+ "gui_tor_connection_ask": "設定を開いて、Torとの接続問題を解決しますか?",
+ "gui_tor_connection_ask_open_settings": "はい",
+ "gui_tor_connection_ask_quit": "終了",
+ "gui_tor_connection_error_settings": "設定でTorとの接続方法を変更してみて下さい。",
+ "gui_tor_connection_canceled": "Torと接続できませんでした。\n\nインターネット接続を確認してから、OnionShareを再開してTorとの接続を設定して下さい。",
+ "gui_tor_connection_lost": "Torから切断されました。",
+ "gui_server_started_after_timeout": "サーバーが起動した前、自動停止タイマーがタイムアウトしました。\n再びファイル共有をして下さい。",
+ "gui_server_timeout_expired": "自動停止タイマーはすでにタイムアウトしています。\n共有し始めるにはリセットして下さい。",
+ "share_via_onionshare": "OnionShareで共有する",
+ "gui_connect_to_tor_for_onion_settings": "onionサービス設定を見るのにTorと接続して下さい",
+ "gui_use_legacy_v2_onions_checkbox": "レガシーアドレスを使用する",
+ "gui_save_private_key_checkbox": "永続的アドレスを使用する",
+ "gui_share_url_description": "このOnionShareアドレスを持つ限り<b>誰でも</b>は<b>Torブラウザー</b>を利用してこのファイルを<b>ダウンロードできます</b>:<img src='{}' />",
+ "gui_receive_url_description": "このOnionShareアドレスを持つ限り<b>誰でも</b>は<b>Torブラウザー</b>を利用してこのPCにファイルを<b>アップロードできます</b>:<img src='{}' />",
+ "gui_url_label_persistent": "このファイル共有には自動停止はありません。<br><br>その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)",
+ "gui_url_label_stay_open": "このファイル共有には自動停止はありません。",
+ "gui_url_label_onetime": "このファイル共有は最初の完了後に停止されます。",
+ "gui_url_label_onetime_and_persistent": "このファイル共有には自動停止はありません。<br><br>その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)",
+ "gui_status_indicator_share_stopped": "共有の準備完了",
+ "gui_status_indicator_share_working": "起動しています…",
+ "gui_status_indicator_share_started": "共有中",
+ "gui_status_indicator_receive_stopped": "受信の準備完了",
+ "gui_status_indicator_receive_working": "起動しています…",
+ "gui_status_indicator_receive_started": "受信中",
+ "gui_file_info": "{} ファイル, {}",
+ "gui_file_info_single": "{} ファイル, {}",
+ "history_in_progress_tooltip": "{} 進行中",
+ "history_completed_tooltip": "{} 完了",
+ "info_in_progress_uploads_tooltip": "{} 進行中のアップロード",
+ "info_completed_uploads_tooltip": "{} 完了のアップロード",
+ "error_cannot_create_downloads_dir": "受信モードフォルダを作成できなかった: {}",
+ "receive_mode_downloads_dir": "受信されるファイルはこのフォルダに保存されます: {}",
+ "receive_mode_warning": "警告:受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。",
+ "gui_receive_mode_warning": "受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。<br><br><b>悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。</b>",
+ "receive_mode_upload_starting": "ファイルサイズ{}のアップロードが実行中",
+ "receive_mode_received_file": "受信した: {}",
+ "gui_mode_share_button": "ファイル共有",
+ "gui_mode_receive_button": "ファイル受信",
+ "gui_settings_receiving_label": "受信設定",
+ "gui_settings_downloads_label": "保存フォルダ",
+ "gui_settings_downloads_button": "選ぶ",
+ "gui_settings_public_mode_checkbox": "公開モード",
+ "systray_close_server_title": "OnionShareサーバーは閉鎖されました",
+ "systray_close_server_message": "ユーザーがサーバーを閉鎖しました",
+ "systray_page_loaded_title": "ページはロードされました",
+ "systray_download_page_loaded_message": "ユーザーがダウンロードページをロードしました",
+ "systray_upload_page_loaded_message": "ユーザーがアップロードページをロードしました",
+ "gui_uploads": "アップロード履歴",
+ "gui_no_uploads": "アップロードはまだありません",
+ "gui_clear_history": "全てをクリアする",
+ "gui_upload_in_progress": "アップロード開始しました {}",
+ "gui_upload_finished_range": "{}を{}にアップロードしました",
+ "gui_upload_finished": "{}をアップロードしました",
+ "gui_download_in_progress": "ダウンロード開始しました {}",
+ "gui_open_folder_error_nautilus": "nautilusを利用できないためフォルダーを開けません。ファイルはここに保存されました: {}",
+ "gui_settings_language_label": "優先言語",
+ "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。",
+ "error_cannot_create_data_dir": "OnionShareのデータフォルダーを作成できませんでした: {}",
+ "receive_mode_data_dir": "受信されるファイルをこのフォルダーにあります: {}",
+ "gui_settings_data_dir_label": "ファイルの保存",
+ "gui_settings_data_dir_browse_button": "閲覧",
+ "systray_page_loaded_message": "OnionShareアドレスはロードされました",
+ "systray_share_started_title": "共有は始めました",
+ "systray_share_started_message": "誰かにファイルを通信し始めました",
+ "systray_share_completed_title": "共有完了",
+ "systray_share_completed_message": "ファイル送信完了",
+ "systray_share_canceled_title": "共有は停止されました",
+ "systray_share_canceled_message": "誰かがファイル受信を停止しました",
+ "systray_receive_started_title": "受信は始めました",
+ "systray_receive_started_message": "誰かがファイルを送信しています",
+ "gui_all_modes_history": "歴史",
+ "gui_all_modes_clear_history": "すべてクリア",
+ "gui_all_modes_transfer_started": "始めました {}",
+ "gui_all_modes_transfer_finished_range": "転送された {} - {}",
+ "gui_all_modes_transfer_finished": "転送された {}",
+ "gui_all_modes_transfer_canceled_range": "停止された {} - {}",
+ "gui_all_modes_transfer_canceled": "停止された {}",
+ "gui_all_modes_progress_complete": "%p%, 経過時間 {0:s} 。",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (計算中)",
+ "gui_all_modes_progress_eta": "{0:s}, 完了予定時刻: {1:s}, %p%",
+ "gui_share_mode_no_files": "送信されたファイルがまだありません",
+ "gui_share_mode_timeout_waiting": "送信完了を待機しています",
+ "gui_receive_mode_no_files": "受信されたファイルがまだありません",
+ "gui_receive_mode_timeout_waiting": "受信完了を待機しています",
+ "gui_settings_onion_label": "Onion設定"
+}
diff --git a/share/locale/ka.json b/share/locale/ka.json
new file mode 100644
index 00000000..0f6541fa
--- /dev/null
+++ b/share/locale/ka.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "პროგრამის დატოვება",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "პროგრამის დატოვება",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "პროგრამის დატოვება",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "არჩევა",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/ko.json b/share/locale/ko.json
new file mode 100644
index 00000000..c5da4e9b
--- /dev/null
+++ b/share/locale/ko.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "어니언 서비스를 포트{0:d} 에서 설정하기.",
+ "preparing_files": "파일들을 압축하기.",
+ "give_this_url": "이 주소를 수신자에게 보내시오:",
+ "give_this_url_stealth": "이 주소와 그리고 HidServAuth 선을 수신자에게 보내시오:",
+ "give_this_url_receive": "이 주소를 발신자에게 보내시오:",
+ "give_this_url_receive_stealth": "이 주소와 그리고 HidServAuth를 발신자에 보내시오:",
+ "ctrlc_to_stop": "서버를 멈추기 위해 Ctrl+C 키를 누르시오",
+ "not_a_file": "{0:s} 는 유효하지 않은 파일입니다.",
+ "not_a_readable_file": "{0:s} 는 읽을수 없는 파일입니다.",
+ "no_available_port": "어니언 서비스를 시작하기 위한 사용 가능한 포트를 찾을수 없었습니다",
+ "other_page_loaded": "주소가 로드되다",
+ "close_on_timeout": "자동멈춤 타이머가 끝났기 때문에 정지되다",
+ "closing_automatically": "다운로드가 완료되었기 때문에 정지되다",
+ "timeout_download_still_running": "다운로드가 완료되기를 기다리는 중입니다",
+ "timeout_upload_still_running": "업로드가 완료되기를 기다리는 중입니다",
+ "large_filesize": "경고: 대용량의 자료를 보내는것은 오래 걸릴수 있습니다",
+ "systray_menu_exit": "종료",
+ "systray_download_started_title": "어니언쉐어 다운로드가 시작됨",
+ "systray_download_started_message": "사용자가 당신의 파일들을 다운로딩 하기 시작했습니다",
+ "systray_download_completed_title": "어니언쉐어 다운로드가 완료됨",
+ "systray_download_completed_message": "사용자가 당신의 파일들을 다운로딩 하는것을 완료했습니다",
+ "systray_download_canceled_title": "어니언쉐어 다운로드가 취소됨",
+ "systray_download_canceled_message": "사용자가 다운로드를 취소했습니다",
+ "systray_upload_started_title": "어니언쉐어 업로드가 시작됨",
+ "systray_upload_started_message": "사용자가 파일들을 당신의 컴퓨터로 업로딩 하는것을 시작했습니다",
+ "help_local_only": "Tor를 사용하지 마시오 (오직 개발자용)",
+ "help_stay_open": "첫 다운로드 후 계속 공유하시오",
+ "help_shutdown_timeout": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오",
+ "help_stealth": "고객 허가를 사용 (고급 수준의)",
+ "help_receive": "그것들을 보내는것 대신 공유를 받으시오",
+ "help_debug": "어니언쉐어 에러들은 표준 출력 장치로 접속하고, 웹 에러들은 디스크로 접속 ",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "삭제",
+ "gui_choose_items": "선택",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "취소 된",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "종료",
+ "gui_quit_warning_dont_quit": "취소",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "설정",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "하지 않음",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "일반 설정",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "제어 포트",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "Password",
+ "gui_settings_password_label": "Password",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "저장",
+ "gui_settings_button_cancel": "취소",
+ "gui_settings_button_help": "도움말",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "종료",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "수익",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "보기",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "모두 삭제",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "선호 언어",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/lg.json b/share/locale/lg.json
new file mode 100644
index 00000000..25cd5c48
--- /dev/null
+++ b/share/locale/lg.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/mk.json b/share/locale/mk.json
new file mode 100644
index 00000000..2273ba1e
--- /dev/null
+++ b/share/locale/mk.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Излези",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Излези",
+ "gui_quit_warning_dont_quit": "Откажи",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "Поставки",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Никогаш",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Општи поставувања",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "Зачувување",
+ "gui_settings_button_cancel": "Откажи",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "Излези",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Преглед",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/nl.json b/share/locale/nl.json
index 3dd74664..39c5ba9f 100644
--- a/share/locale/nl.json
+++ b/share/locale/nl.json
@@ -1,125 +1,189 @@
{
- "config_onion_service": "Onion service configureren op poort {0:d}.",
- "preparing_files": "Bestanden om te delen aan het voorbereiden.",
- "wait_for_hs": "Wachten op gereed zijn van HS:",
- "wait_for_hs_trying": "Proberen...",
- "wait_for_hs_nope": "Nog niet gereed.",
- "wait_for_hs_yup": "Gereed!",
- "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:",
- "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:",
- "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen",
- "not_a_file": "{0:s} is geen bestand.",
+ "config_onion_service": "Onion-dienst configureren op poort {0:d}.",
+ "preparing_files": "Bezig met comprimeren van bestanden.",
+ "give_this_url": "Geef dit adres aan de ontvanger:",
+ "give_this_url_stealth": "Geef dit adres en de HidServAuth-regel aan de ontvanger:",
+ "ctrlc_to_stop": "Druk op Ctrl+C om de server te stoppen",
+ "not_a_file": "{0:s} is geen geldig bestand.",
"not_a_readable_file": "{0:s} is geen leesbaar bestand.",
- "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.",
- "download_page_loaded": "Downloadpagina geladen",
- "other_page_loaded": "URL geladen",
- "close_on_timeout": "Sluit automatisch omdat timeout bereikt is",
- "closing_automatically": "Sluit automatisch omdat download gereed is",
- "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten",
- "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren",
- "error_tails_invalid_port": "Ongeldige waarde, poort moet een integer zijn",
- "error_tails_unknown_root": "Onbekende fout met het Tails root proces",
+ "no_available_port": "Er is geen poort beschikbaar om de onion-dienst op te starten",
+ "other_page_loaded": "Adres geladen",
+ "close_on_timeout": "Gestopt omdat de automatische time-out bereikt is",
+ "closing_automatically": "Gestopt omdat de download is afgerond",
+ "timeout_download_still_running": "Bezig met wachten op afronden van download",
+ "large_filesize": "Waarschuwing: het versturen van grote bestanden kan uren duren",
"systray_menu_exit": "Afsluiten",
- "systray_download_started_title": "OnionShare download gestart",
+ "systray_download_started_title": "OnionShare-download gestart",
"systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden",
- "systray_download_completed_title": "OnionShare download gereed",
+ "systray_download_completed_title": "OnionShare-download afgerond",
"systray_download_completed_message": "De gebruiker is klaar met downloaden",
- "systray_download_canceled_title": "OnionShare download afgebroken",
+ "systray_download_canceled_title": "OnionShare-download afgebroken",
"systray_download_canceled_message": "De gebruiker heeft de download afgebroken",
- "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling",
- "help_stay_open": "Laat verborgen service draaien nadat download gereed is",
- "help_shutdown_timeout": "Sluit de Onion service na N seconden",
- "help_transparent_torification": "Mijn systeem gebruikt Tor als proxyserver",
- "help_stealth": "Maak stealth Onion service (geavanceerd)",
- "help_debug": "Log fouten naar harde schijf",
+ "help_local_only": "Tor niet gebruiken (alleen voor ontwikkelingsdoeleinden)",
+ "help_stay_open": "Blijven delen na afronden van eerste download",
+ "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden",
+ "help_stealth": "Client-authorisatie gebruiken (geavanceerd)",
+ "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk",
"help_filename": "Lijst van bestanden of mappen om te delen",
- "help_config": "Pad naar een JSON configuratie bestand (optioneel)",
- "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer",
+ "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)",
+ "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer om het delen te starten",
"gui_add": "Toevoegen",
"gui_delete": "Verwijder",
"gui_choose_items": "Kies",
- "gui_start_server": "Start server",
- "gui_stop_server": "Stop server",
"gui_copy_url": "Kopieer URL",
"gui_copy_hidservauth": "Kopieer HidServAuth",
- "gui_downloads": "Downloads:",
+ "gui_downloads": "Download Geschiedenis",
"gui_canceled": "Afgebroken",
- "gui_copied_url": "URL gekopieerd naar klembord",
+ "gui_copied_url": "OnionShare adres gekopieerd naar klembord",
"gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord",
- "gui_starting_server1": "Tor onion service wordt gestart...",
- "gui_starting_server2": "Bestanden verwerken...",
- "gui_please_wait": "Moment geduld...",
- "error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken",
- "error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar",
- "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie",
- "gui_download_progress_complete": "%p%, Tijd verstreken: {0:s}",
- "gui_download_progress_starting": "{0:s}, %p% (ETA berekenen)",
- "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
- "version_string": "Onionshare {0:s} | https://onionshare.org/",
- "gui_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.",
+ "gui_please_wait": "Aan het starten... Klik om te annuleren.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} verstreken.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (berekenen)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_share_quit_warning": "Je bent in het proces van bestanden versturen. Weet je zeker dat je OnionShare af wilt sluiten?",
"gui_quit_warning_quit": "Afsluiten",
- "gui_quit_warning_dont_quit": "Niet afsluiten",
- "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.",
- "zip_progress_bar_format": "Bestanden verwerken: %p%",
- "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.",
- "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.",
+ "gui_quit_warning_dont_quit": "Annuleren",
+ "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.",
+ "zip_progress_bar_format": "Comprimeren: %p%",
+ "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.",
+ "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.",
"gui_settings_window_title": "Instellingen",
- "gui_settings_stealth_label": "Stealth (geavanceerd)",
- "gui_settings_stealth_option": "Maak stealth onion services",
- "gui_settings_stealth_option_details": "Dit maakt OnionShare veiliger, maar ook lastiger voor de ontvanger om te verbinden.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">Meer informatie</a>.",
- "gui_settings_autoupdate_label": "Controleer voor updates",
- "gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn",
+ "gui_settings_stealth_option": "Gebruik cliëntautorisatie",
+ "gui_settings_autoupdate_label": "Controleer op nieuwe versies",
+ "gui_settings_autoupdate_option": "Laat me weten als er een nieuwe versie beschikbaar is",
"gui_settings_autoupdate_timestamp": "Laatste controle: {}",
"gui_settings_autoupdate_timestamp_never": "Nooit",
- "gui_settings_autoupdate_check_button": "Controleer voor update",
- "gui_settings_sharing_label": "Deel opties",
- "gui_settings_close_after_first_download_option": "Stop delen na eerste download",
- "gui_settings_systray_notifications": "Laat desktop notificaties zien",
+ "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie",
+ "gui_settings_sharing_label": "Instelling voor delen",
+ "gui_settings_close_after_first_download_option": "Stop met delen na eerste download",
"gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?",
- "gui_settings_connection_type_bundled_option": "Gebruik Tor die is meegeleverd met OnionShare",
- "gui_settings_connection_type_automatic_option": "Probeer automatische configuratie met Tor Browser",
+ "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare",
+ "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser",
"gui_settings_connection_type_control_port_option": "Verbinden via controle poort",
"gui_settings_connection_type_socket_file_option": "Verbinden via socket bestand",
- "gui_settings_connection_type_test_button": "Test Tor instellingen",
+ "gui_settings_connection_type_test_button": "Test Connectie naar Tor",
"gui_settings_control_port_label": "Controle poort",
"gui_settings_socket_file_label": "Socket bestand",
"gui_settings_socks_label": "SOCKS poort",
- "gui_settings_authenticate_label": "Tor authenticatie opties",
- "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie",
+ "gui_settings_authenticate_label": "Tor authenticatie instellingen",
+ "gui_settings_authenticate_no_auth_option": "Geen authenticatie, of cookie authenticatie",
"gui_settings_authenticate_password_option": "Wachtwoord",
- "gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Wachtwoord",
- "gui_settings_cookie_label": "Cookie pad",
"gui_settings_button_save": "Opslaan",
"gui_settings_button_cancel": "Annuleren",
"gui_settings_button_help": "Help",
- "gui_settings_shutdown_timeout_choice": "Auto-stop timer instellen?",
- "gui_settings_shutdown_timeout": "Stop delen om:",
+ "gui_settings_shutdown_timeout": "Stop het delen om:",
"settings_saved": "Instellingen opgeslagen in {}",
- "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.",
- "settings_error_automatic": "Kan geen verbinding maken met de Tor controller. Draait Tor Browser in de achtergrond? Deze kan je verkrijgen via https://www.torproject.org/.",
+ "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat je instellingen nergens op slaan.",
+ "settings_error_automatic": "Kon geen verbinding maken met de Tor controller. Draait Tor Browser (beschikbaar via torproject.org) in de achtergrond?",
"settings_error_socket_port": "Kan geen verbinding maken met de Tor controller op {}:{}.",
"settings_error_socket_file": "Kan geen verbinding maken met de Tor controller via socket bestand {}.",
"settings_error_auth": "Verbonden met {}:{}, maar kan niet authenticeren. Misschien is het geen Tor controller?",
"settings_error_missing_password": "Verbonden met Tor controller, maar het heeft een wachtwoord nodig voor authenticatie.",
- "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.",
- "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.",
- "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.",
- "settings_error_bundled_tor_canceled": "Het Tor is afgesloten voordat het kon verbinden.",
+ "settings_error_unreadable_cookie_file": "Verbonden met de Tor controller, maar het wachtwoord kan onjuist zijn, of je gebruiker heeft geen toestemming om het cookie bestand te lezen.",
+ "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de ontwikkelaarsmodus op Windows of macOS.",
+ "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?",
"settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}",
- "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}",
- "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.",
+ "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.",
+ "error_tor_protocol_error": "Er was een fout met Tor: {}",
"connecting_to_tor": "Verbinden met het Tor network",
- "update_available": "Er is een OnionShare update beschikbaar. <a href='{}'>Klik hier</a> om te downloaden.<br><br>Geinstalleerde versie: {}<br>Laatste versie: {}",
- "update_error_check_error": "Fout bij controleren voor updates: Misschien ben je niet verbonden met Tor, of misschien is de OnionShare website down.",
- "update_error_invalid_latest_version": "Fout bij controleren voor updates: De OnionShare website zegt dat de laatste versie '{}' is, maar dat lijkt geen valide versie te zijn.",
- "update_not_available": "Je draait de laatste versie van OnionShare.",
- "gui_tor_connection_ask": "Wil je de OnionShare instellingen openen om het verbindingsprobleem met Tor op te lossen?",
- "gui_tor_connection_ask_open_settings": "Open Instellingen",
+ "update_available": "Nieuwe OnionShare is uitgekomen. <a href='{}'>Klik hier</a> om hem te krijgen.<br><br>Jij gebruikt {} and de laatste is {}.",
+ "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…",
+ "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of de OnionShare website is niet beschikbaar?",
+ "update_not_available": "Je draait de laatst beschikbare OnionShare.",
+ "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?",
+ "gui_tor_connection_ask_open_settings": "Ja",
"gui_tor_connection_ask_quit": "Afsluiten",
- "gui_tor_connection_error_settings": "Probeer de instellingen hoe OnionShare verbind met het Tor network aan te passen in Instellingen.",
- "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.",
- "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.",
- "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.",
- "share_via_onionshare": "Deel via OnionShare"
+ "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.",
+ "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.",
+ "gui_server_started_after_timeout": "De auto-stop timer liep af voordat de server startte.\nMaak een nieuwe share aan.",
+ "gui_server_timeout_expired": "De auto-stop timer is al verlopen.\nStel een nieuwe tijd in om te beginnen met delen.",
+ "share_via_onionshare": "Deel via OnionShare",
+ "give_this_url_receive": "Geef dit adres aan de afzender:",
+ "give_this_url_receive_stealth": "Geef dit adres en de HidServAuth-regel aan de afzender:",
+ "systray_upload_started_title": "OnionShare-upload gestart",
+ "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer",
+ "help_receive": "Bestanden ontvangen in plaats van ze versturen",
+ "timeout_upload_still_running": "Wachten op voltooiing van de upload",
+ "gui_share_start_server": "Start met delen",
+ "gui_share_stop_server": "Stop met delen",
+ "gui_share_stop_server_shutdown_timeout": "Stop met Delen ({}s resterend)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer eindigt bij {}",
+ "gui_receive_start_server": "Start Ontvangstmodus",
+ "gui_receive_stop_server": "Stop Ontvangstmodus",
+ "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangstmodus ({}s resterend)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer stopt bij {}",
+ "gui_no_downloads": "Nog Geen Downloads",
+ "gui_copied_url_title": "Gekopieerd OnionShare Adres",
+ "gui_copied_hidservauth_title": "HidServAuth gekopieerd",
+ "gui_quit_title": "Niet zo snel",
+ "gui_receive_quit_warning": "Je bent in het proces van bestanden ontvangen. Weet je zeker dat je OnionShare af wilt sluiten?",
+ "gui_settings_whats_this": "<a href='{0:s}'>1Wat is dit?</a>2",
+ "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je \nklikken om je HidServAuth te kopiëren.",
+ "gui_settings_general_label": "Algemene instellingen",
+ "gui_settings_tor_bridges": "Tor bridge ondersteuning",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Gebruik ingebouwde obfs4 pluggable transports",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Gebruik ingebouwde pluggable transports (vereist obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Gebruik ingebouwde meek_lite (Azure) pluggable transports",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Gebruik ingebouwde meek_lite (Azure) pluggable transports (vereist obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Waarschuwing: De meek_lite bridges zijn erg kostbaar voor het Tor Project om uit te voeren. <br><br> Gebruik ze alleen als je niet direct met Tor kan verbinden, via obfs4 transports, of andere normale bridges.",
+ "gui_settings_tor_bridges_custom_radio_option": "Gebruik custom bridges",
+ "gui_settings_tor_bridges_custom_label": "Je kan bridges krijgen via <a href=\"https://bridges.torproject.org/options\">1https://bridges.torproject.org</a>2",
+ "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. \nControleer ze of voeg andere toe.",
+ "gui_settings_shutdown_timeout_checkbox": "Gebruik auto-stop timer",
+ "error_tor_protocol_error_unknown": "Er was een onbekende fout met Tor",
+ "error_invalid_private_key": "Dit type privésleutel wordt niet ondersteund",
+ "gui_tor_connection_lost": "De verbinding met Tor is verbroken.",
+ "gui_use_legacy_v2_onions_checkbox": "Gebruik ouderwetse adressen",
+ "gui_save_private_key_checkbox": "Gebruik een blijvend adres",
+ "gui_share_url_description": "<b>1Iedereen</b>2 met dit OnionShare adres kan je bestanden <b>3binnenhalen</b>4 met de <b>5Tor Browser</b>6: <img src='{}' />",
+ "gui_receive_url_description": "<b>1Iedereen</b>2 met dit OnionShare adres kan bestanden op je computer <b>3plaatsen</b>4 met de <b>5Tor Browser</b>6: <img src='{}' />7",
+ "gui_url_label_persistent": "Deze share stopt niet vanzelf. <br><br>Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)",
+ "gui_url_label_stay_open": "Deze share stopt niet automatisch.",
+ "gui_url_label_onetime": "Deze share stopt na de eerste voltooiing.",
+ "gui_url_label_onetime_and_persistent": "Deze share stopt niet vanzelf. <br><br>Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)",
+ "gui_status_indicator_share_stopped": "Klaar om te delen",
+ "gui_status_indicator_share_working": "Starten…",
+ "gui_status_indicator_share_started": "Aan het delen",
+ "gui_status_indicator_receive_stopped": "Klaar om te ontvangen",
+ "gui_status_indicator_receive_working": "Starten…",
+ "gui_status_indicator_receive_started": "Ontvangen",
+ "gui_file_info": "{} bestanden, {}",
+ "gui_file_info_single": "{} bestand, {}",
+ "history_in_progress_tooltip": "{} bezig",
+ "history_completed_tooltip": "{} klaar",
+ "info_in_progress_uploads_tooltip": "{} upload(s) zijn bezig",
+ "info_completed_uploads_tooltip": "de {} upload(s) zijn klaar",
+ "error_cannot_create_downloads_dir": "Kon de ontvangst modus map niet aanmaken: {}",
+ "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}",
+ "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.",
+ "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. <br><br><b>Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.</b>",
+ "receive_mode_upload_starting": "Upload met totale grootte {} is aan het starten",
+ "receive_mode_received_file": "Ontvangen: {}",
+ "gui_mode_share_button": "Deel Bestanden",
+ "gui_mode_receive_button": "Ontvang Bestanden",
+ "gui_settings_receiving_label": "Instellingen voor Ontvangen",
+ "gui_settings_downloads_label": "Sla bestanden op naar",
+ "gui_settings_downloads_button": "Surf",
+ "gui_settings_public_mode_checkbox": "Openbaarmodus",
+ "systray_close_server_title": "OnionShare Server Afgesloten",
+ "systray_close_server_message": "Een gebruiker heeft de server gestopt",
+ "systray_page_loaded_title": "OnionShare Pagina Geladen",
+ "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen",
+ "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen",
+ "gui_uploads": "Upload Geschiedenis",
+ "gui_no_uploads": "Er Zijn Nog Geen Uploads",
+ "gui_clear_history": "Wis Alles",
+ "gui_upload_in_progress": "Upload Is Gestart{}",
+ "gui_upload_finished_range": "{} is naar {} gestuurd",
+ "gui_upload_finished": "Verzonden {}",
+ "gui_download_in_progress": "Downloaden Gestart {}",
+ "gui_open_folder_error_nautilus": "Kan de map niet openen omdat nautilus niet beschikbaar is. Het bestand staat hier: {}",
+ "gui_settings_language_label": "Voorkeurstaal",
+ "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.",
+ "gui_add_files": "Voeg bestanden toe",
+ "gui_add_folder": "Voeg map toe",
+ "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien"
}
diff --git a/share/locale/no.json b/share/locale/no.json
index 8b131038..9d67e6fa 100644
--- a/share/locale/no.json
+++ b/share/locale/no.json
@@ -1,10 +1,220 @@
{
- "connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0:d}.",
- "cant_connect_ctrlport": "Klarte ikke å koble til Tors kontroll-porter {0:s}. Sjekk at Tor kjører.",
- "give_this_url": "Gi personen du vil sende filen til denne URL-en:",
- "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.",
+ "give_this_url": "Gi denne adressen til mottakeren:",
+ "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe tjeneren",
"not_a_file": "{0:s} er ikke en fil.",
- "gui_copied_url": "Kopierte URL-en til utklippstavlen",
- "download_page_loaded": "Nedlastingsside lastet",
- "other_page_loaded": "En annen side har blitt lastet"
+ "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle",
+ "other_page_loaded": "En annen side har blitt lastet",
+ "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.",
+ "preparing_files": "Pakker sammen filer.",
+ "give_this_url_stealth": "Gi denne adressen og HidServAuth-linjen til mottakeren:",
+ "give_this_url_receive": "Gi denne adressen til avsenderen:",
+ "give_this_url_receive_stealth": "Gi denne adressen og HidServAuth-linjen til avsenderen:",
+ "not_a_readable_file": "{0:s} er ikke en lesbar fil.",
+ "no_available_port": "Fant ikke tilgjengelig port for oppstart av løktjenesten",
+ "close_on_timeout": "Stoppet fordi tidsavbruddsuret gikk ut",
+ "closing_automatically": "Stoppet fordi nedlasting fullførtes",
+ "timeout_download_still_running": "Venter på at nedlastingen skal fullføres",
+ "large_filesize": "Advarsel: forsendelse av stor deling kan ta timer",
+ "systray_menu_exit": "Avslutt",
+ "systray_download_started_title": "OnionShare-nedlasting startet",
+ "systray_download_started_message": "En bruker startet nedlasting av filene dine",
+ "systray_download_completed_title": "OnionShare-nedlasting fullført",
+ "systray_download_completed_message": "Brukeren fullførte nedlasting av filene dine",
+ "systray_download_canceled_title": "OnionShare-nedlasting avbrutt",
+ "systray_download_canceled_message": "Brukeren avbrøt nedlastingen",
+ "systray_upload_started_title": "OnionShare-opplasting startet",
+ "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin",
+ "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)",
+ "help_stay_open": "Fortsett å dele etter at filene har blitt sendt",
+ "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder",
+ "help_stealth": "Bruk klientidentifisering (avansert)",
+ "help_receive": "Motta delinger istedenfor å sende dem",
+ "help_debug": "Log OnionShare-feil til stdout, og vev-feil til disk",
+ "help_filename": "Liste over filer eller mapper å dele",
+ "help_config": "Egendefinert JSON-oppsettsfil (valgfri)",
+ "gui_drag_and_drop": "Dra og slipp filer og mapper\nfor å starte deling",
+ "gui_add": "Legg til",
+ "gui_delete": "Slett",
+ "gui_choose_items": "Velg",
+ "gui_share_start_server": "Start deling",
+ "gui_share_stop_server": "Stopp deling",
+ "gui_share_stop_server_shutdown_timeout": "Stopp deling ({}s gjenstår)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}",
+ "gui_receive_start_server": "Start mottaksmodus",
+ "gui_receive_stop_server": "Stopp mottaksmodus",
+ "gui_receive_stop_server_shutdown_timeout": "Stopp mottaksmodus ({}s gjenstår)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}",
+ "gui_copy_url": "Kopier nettadresse",
+ "gui_copy_hidservauth": "Kopier HidServAuth",
+ "gui_downloads": "Nedlastingshistorikk",
+ "gui_no_downloads": "Ingen nedlastinger enda.",
+ "gui_canceled": "Avbrutt",
+ "gui_copied_url_title": "Kopierte OnionShare-adressen",
+ "gui_copied_hidservauth_title": "Kopierte HidServAuth-linje",
+ "gui_copied_hidservauth": "HidServAuth-linje kopiert til utklippstavle",
+ "gui_please_wait": "Starter… Klikk for å avbryte.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} forløpt.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (regner ut)",
+ "gui_download_upload_progress_eta": "{0:s}, anslått ferdigstilt: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Hold an",
+ "gui_share_quit_warning": "Filer er i ferd med å bli sendt. Er du sikker på at du ønsker å avslutte OnionShare?",
+ "gui_receive_quit_warning": "Du har ikke fått overført alle filene til deg enda. Er du sikker på at du ønsker å avslutte OnionShare?",
+ "gui_quit_warning_quit": "Avslutt",
+ "gui_quit_warning_dont_quit": "Avbryt",
+ "error_rate_limit": "Noen har prøvd adressen din for mange ganger, som kan betyr at de prøver å gjette seg fram til den, OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.",
+ "zip_progress_bar_format": "Pakker sammen: %p%",
+ "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-nettleseren 6.5) og python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.",
+ "gui_settings_window_title": "Innstillinger",
+ "gui_settings_whats_this": "<a href='{0:s}'>Hva er dette?</a>",
+ "gui_settings_stealth_option": "Bruk klientidentifisering",
+ "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.",
+ "gui_settings_autoupdate_label": "Se etter ny versjon",
+ "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig",
+ "gui_settings_autoupdate_timestamp": "Sist sjekket: {}",
+ "gui_settings_autoupdate_timestamp_never": "Aldri",
+ "gui_settings_autoupdate_check_button": "Se etter ny versjon",
+ "gui_settings_general_label": "Hovedinnstillinger",
+ "gui_settings_sharing_label": "Delingsinnstillinger",
+ "gui_settings_close_after_first_download_option": "Stopp deling etter at filene har blitt sendt",
+ "gui_settings_connection_type_label": "Hvordan skal OnionShare koble seg til Tor?",
+ "gui_settings_connection_type_bundled_option": "Bruk Tor-versjonen som kommer med OnionShare",
+ "gui_settings_connection_type_automatic_option": "Forsøk automatisk oppsett med Tor-nettleser",
+ "gui_settings_connection_type_control_port_option": "Koble til ved bruk av kontrollport",
+ "gui_settings_connection_type_socket_file_option": "Koble til ved bruk av socket-fil",
+ "gui_settings_connection_type_test_button": "Test tilkobling til Tor",
+ "gui_settings_control_port_label": "Kontrollport",
+ "gui_settings_socket_file_label": "Socket-fil",
+ "gui_settings_socks_label": "SOCKS-port",
+ "gui_settings_authenticate_label": "Tor-identitetsbekreftelsesinnstillinger",
+ "gui_settings_authenticate_no_auth_option": "Ingen identitetsbekreftelse, eller kakeidentifiseringsbekreftelse",
+ "gui_settings_authenticate_password_option": "Passord",
+ "gui_settings_password_label": "Passord",
+ "gui_settings_tor_bridges": "Støtte for Tor-bro",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Ikke benytt broer",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Bruk innebygd pluggbare obfs4-transporter",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare obfs4-transporter (krever obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bruk innebygd pluggbare meek_lite (Azure) transporter",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare meek_lite (Azure) transporter (krever obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Advarsel: Meek-lite-broene er veldig kostbare å kjøre for Tor-prosjektet.<br><br>Kun bruk dem hvis direkte tilkobling til Tor ikke virker, via obfs-transporter, eller andre normale broer.",
+ "gui_settings_tor_bridges_custom_radio_option": "Bruk egendefinerte broer",
+ "gui_settings_tor_bridges_custom_label": "Du kan hente broer fra <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Ingen av broene du la til virker.\nDobbeltsjekk dem eller legg til andre.",
+ "gui_settings_button_save": "Lagre",
+ "gui_settings_button_cancel": "Avbryt",
+ "gui_settings_button_help": "Hjelp",
+ "gui_settings_shutdown_timeout_checkbox": "Bruk tidsavbruddsur",
+ "gui_settings_shutdown_timeout": "Stopp deling ved:",
+ "settings_saved": "Innstillinger lagret i {}",
+ "settings_error_unknown": "Kan ikke koble til Tor-kontroller fordi innstillingene dine ikke gir mening.",
+ "settings_error_automatic": "Kunne ikke koble til Tor-kontrolleren. Kjører Tor-nettleseren (tilgjengelig fra torproject.org) i bakgrunnen?",
+ "settings_error_socket_port": "Kan ikke koble til Tor-kontroller på {}:{}.",
+ "settings_error_socket_file": "Kan ikke koble til Tor-kontroller ved bruk av socket-fil {}.",
+ "settings_error_auth": "Tilkoblet til {}:{}, men kan ikke identitetsbekrefte. Kanskje dette ikke er en Tor-kontroller?",
+ "settings_error_missing_password": "Tilkoblet til Tor-kontroller, men den krever et passord for å identitetsbekrefte.",
+ "settings_error_unreadable_cookie_file": "Tilkoblet til Tor-kontrolleren, men passordet kan være galt, eller så har ikke brukeren din tilgang til å lese fra kakefilen.",
+ "settings_error_bundled_tor_not_supported": "Bruk av Tor-versjonen som kommer med OnionShare fungerer ikke i utviklermodus på Windows eller macOS.",
+ "settings_error_bundled_tor_timeout": "Det tar for lang tid å koble til Tor. Kanskje du ikke er koblet til Internett, eller har du kanskje en unøyaktig systemklokke?",
+ "settings_error_bundled_tor_broken": "OnionShare kunne ikke koble til Tor i bakgrunnen:\n{}",
+ "settings_test_success": "Koblet til Tor-kontrolleren.\n\nTor-versjon: {}.\nStøtter flyktige løktjenester: {}.\nStøtter klientidentifisering: {}.\nStøtter nestegenerasjons .onion-adresser: {}.",
+ "error_tor_protocol_error": "Feil med Tor: {}",
+ "error_tor_protocol_error_unknown": "Ukjent feil med Tor",
+ "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet",
+ "connecting_to_tor": "Kobler til Tor-nettverket",
+ "update_available": "Ny OnionShare lansert. <a href='{}'>Klikk her</a> for å hente det.<br><br>Du bruker {} og nyeste versjon er {}.",
+ "update_error_check_error": "Kunne ikke sjekke etter nye versjoner: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…",
+ "update_error_invalid_latest_version": "Kunne ikke sjekke etter ny versjon: Kanskje du ikke er koblet til Tor, eller kanskje OnionShare-nettsiden er nede?",
+ "update_not_available": "Du kjører siste OnionShare.",
+ "gui_tor_connection_ask": "Åpne innstillingene for å ordne opp i tilkoblingen til Tor?",
+ "gui_tor_connection_ask_open_settings": "Ja",
+ "gui_tor_connection_ask_quit": "Avslutt",
+ "gui_tor_connection_error_settings": "Prøv å endre hvordan OnionShare kobler til Tor-nettverket i innstillingene.",
+ "gui_tor_connection_canceled": "Kunne ikke koble til Tor.\n\nForsikre deg om at du er koblet til Internett, åpne så OnionShare igjen, og sett opp dets tilkobling til Tor.",
+ "gui_tor_connection_lost": "Frakoblet fra Tor.",
+ "gui_server_started_after_timeout": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.",
+ "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.",
+ "share_via_onionshare": "OnionShare det",
+ "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser",
+ "gui_save_private_key_checkbox": "Bruk en vedvarende adresse",
+ "gui_share_url_description": "<b>Alle</b> som har denne OnionShare-adressen kan <b>Laste ned</b> filene dine ved bruk av <b>Tor-nettleseren</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Alle</b> som har denne OnionShare-adressen kan <b>Laste opp</b> filer til din datamaskin ved bruk av <b>Tor-nettleseren</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.<br><br>Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)",
+ "gui_url_label_stay_open": "Denne delingen vil ikke stoppe automatisk.",
+ "gui_url_label_onetime": "Denne delingen vil stoppe etter første fullføring.",
+ "gui_url_label_onetime_and_persistent": "Delingen vil ikke stoppe automatisk.<br><br>Hver påfølgende deling vil gjenbruke adressen. (For å bruke engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)",
+ "gui_status_indicator_share_stopped": "Klar til å dele",
+ "gui_status_indicator_share_working": "Starter…",
+ "gui_status_indicator_share_started": "Deler",
+ "gui_status_indicator_receive_stopped": "Klar til mottak",
+ "gui_status_indicator_receive_working": "Starter…",
+ "gui_status_indicator_receive_started": "Mottar",
+ "gui_file_info": "{} filer, {}",
+ "gui_file_info_single": "{} fil, {}",
+ "info_in_progress_downloads_tooltip": "{} nedlasting(er) underveis",
+ "info_completed_downloads_tooltip": "{} nedlasting(er) fullført",
+ "info_in_progress_uploads_tooltip": "{} opplasting(er) underveis",
+ "info_completed_uploads_tooltip": "{} nedlasting(er) fullført",
+ "error_cannot_create_downloads_dir": "Kunne ikke opprette mottaksmodusmappe: {}",
+ "error_downloads_dir_not_writable": "Mottaksmodusmappen er skrivebeskyttet: {}",
+ "receive_mode_downloads_dir": "Filer sendt til deg vil vises i denne mappen: {}",
+ "receive_mode_warning": "Advarsel: Mottaksmodus lar folk laste opp filer til din datamaskin. Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.",
+ "gui_receive_mode_warning": "Mottaksmodus lar folk laste opp filer til din datamaskin.<br><br><b>Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.</b>",
+ "receive_mode_upload_starting": "Opplasting av total størrelse {} starter",
+ "receive_mode_received_file": "Mottatt: {}",
+ "gui_mode_share_button": "Del filer",
+ "gui_mode_receive_button": "Motta filer",
+ "gui_settings_receiving_label": "Mottaksinnstillinger",
+ "gui_settings_downloads_label": "Lagre filer i",
+ "gui_settings_downloads_button": "Utforstk",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Mottaksmodus kan stoppes av avsenderen",
+ "gui_settings_public_mode_checkbox": "Offentlig modus",
+ "systray_close_server_title": "OnionShare-tjener lukket",
+ "systray_close_server_message": "En bruker stengte tjeneren",
+ "systray_page_loaded_title": "Side innlastet",
+ "systray_download_page_loaded_message": "En bruker lastet inn nedlastingssiden",
+ "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden",
+ "gui_uploads": "Opplastingshistorikk",
+ "gui_no_uploads": "Ingen opplastinger enda.",
+ "gui_clear_history": "Tøm alt",
+ "gui_upload_in_progress": "Opplasting startet {}",
+ "gui_upload_finished_range": "Lastet opp {} til {}",
+ "gui_upload_finished": "Lastet opp {}",
+ "gui_open_folder_error_nautilus": "Kan ikke åpne mappe fordi nautilus ikke er tilgjengelig. Filen er her: {}",
+ "history_in_progress_tooltip": "{} underveis",
+ "history_completed_tooltip": "{} fullført",
+ "gui_download_in_progress": "Nedlasting startet {}",
+ "gui_settings_language_label": "Foretrukket språk",
+ "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg.",
+ "timeout_upload_still_running": "Venter på at opplastingen fullføres",
+ "gui_add_files": "Legg til filer",
+ "gui_add_folder": "Legg til mappe",
+ "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger",
+ "error_cannot_create_data_dir": "Kunne ikke opprette OnionShare-datamappe: {}",
+ "receive_mode_data_dir": "Filers sendt til deg havner i denne mappen: {}",
+ "gui_settings_data_dir_label": "Lagre filer i",
+ "gui_settings_data_dir_browse_button": "Utforsk",
+ "systray_page_loaded_message": "OnionShare-adresse innlastet",
+ "systray_share_started_title": "Deling startet",
+ "systray_share_started_message": "Begynner å sende filer til noen",
+ "systray_share_completed_title": "Deling fullført",
+ "systray_share_completed_message": "Forsendelse av filer utført",
+ "systray_share_canceled_title": "Deling avbrutt",
+ "systray_share_canceled_message": "Noen avbrøt mottak av filene dine",
+ "systray_receive_started_title": "Mottak startet",
+ "systray_receive_started_message": "Noen sender filer til deg",
+ "gui_all_modes_history": "Historikk",
+ "gui_all_modes_clear_history": "Tøm alt",
+ "gui_all_modes_transfer_started": "Startet {}",
+ "gui_all_modes_transfer_finished_range": "Overført {} - {}",
+ "gui_all_modes_transfer_finished": "Overført {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} forløpt.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (kalkulerer)",
+ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_share_mode_no_files": "Ingen filer sendt enda",
+ "gui_share_mode_timeout_waiting": "Venter på fullføring av forsendelse",
+ "gui_receive_mode_no_files": "Ingen filer mottatt enda",
+ "gui_receive_mode_timeout_waiting": "Venter på fullføring av mottak",
+ "gui_all_modes_transfer_canceled_range": "Avbrutt {} - {}",
+ "gui_all_modes_transfer_canceled": "Avbrutt {}"
}
diff --git a/share/locale/pa.json b/share/locale/pa.json
new file mode 100644
index 00000000..a2a967cc
--- /dev/null
+++ b/share/locale/pa.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "ਬਾਹਰ",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "ਬਾਹਰ",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "ਜਨਰਲ ਸੈਟਿੰਗਜ਼",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "ਬਾਹਰ",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/pl.json b/share/locale/pl.json
new file mode 100644
index 00000000..f10a30ce
--- /dev/null
+++ b/share/locale/pl.json
@@ -0,0 +1,188 @@
+{
+ "config_onion_service": "Konfiguruję usługę onion na porcie {0:d}.",
+ "preparing_files": "Kompresuję pliki.",
+ "give_this_url": "Przekaż ten adres odbiorcy:",
+ "give_this_url_stealth": "Przekaż ten adres i linijkę HidServAuth odbiorcy:",
+ "give_this_url_receive": "Przekaż ten adres do nadawcy:",
+ "give_this_url_receive_stealth": "Przekaż ten adres i linijkę HidServAuth do nadawcy:",
+ "ctrlc_to_stop": "Przyciśnij kombinację klawiszy Ctrl i C aby zatrzymać serwer",
+ "not_a_file": "{0:s} nie jest prawidłowym plikiem.",
+ "not_a_readable_file": "{0:s} nie jest plikiem do odczytu.",
+ "no_available_port": "Nie można znaleźć dostępnego portu aby włączyć usługę onion",
+ "other_page_loaded": "Adres został wczytany",
+ "close_on_timeout": "Zatrzymano, gdyż upłynął czas",
+ "closing_automatically": "Zatrzymano, gdyż pobieranie zostało ukończone",
+ "timeout_download_still_running": "Czekam na ukończenie pobierania",
+ "large_filesize": "Uwaga: Wysyłanie dużego pliku może zająć kilka godzin",
+ "systray_menu_exit": "Wyjście",
+ "systray_download_started_title": "Pobieranie OnionShare zostało rozpoczęte",
+ "systray_download_started_message": "Użytkownik rozpoczął ściąganie Twoich plików",
+ "systray_download_completed_title": "Pobieranie OnionShare skończone",
+ "systray_download_completed_message": "Użytkownik ukończył ściąganie Twoich plików",
+ "systray_download_canceled_title": "Pobieranie OnionShare zostało anulowane",
+ "systray_download_canceled_message": "Użytkownik anulował pobieranie",
+ "systray_upload_started_title": "Wysyłanie OnionShare rozpoczęte",
+ "systray_upload_started_message": "Użytkownik rozpoczął wysyłanie plików na Twój komputer",
+ "help_local_only": "Nie wykorzystuj sieci Tor (opcja zaawansowana)",
+ "help_stay_open": "Kontynuuj udostępnianie po pierwszym pobraniu",
+ "help_shutdown_timeout": "Przestań udostępniać po określonym czasie w sekundach",
+ "help_stealth": "Korzystaj z weryfikacji klienta (zaawansowane)",
+ "help_receive": "Odbieraj dane zamiast je wysyłać",
+ "help_debug": "Zapisz błędy OnionShare do stdout i zapisz błędy sieciowe na dysku",
+ "help_filename": "Lista plików i folderów do udostępnienia",
+ "help_config": "Lokalizacja niestandarowego pliku konfiguracyjnego JSON (opcjonalne)",
+ "gui_drag_and_drop": "Przeciągnij i upuść pliki i foldery\naby je udostępnić",
+ "gui_add": "Dodaj",
+ "gui_delete": "Usuń",
+ "gui_choose_items": "Wybierz",
+ "gui_share_start_server": "Rozpocznij udostępnianie",
+ "gui_share_stop_server": "Zatrzymaj udostępnianie",
+ "gui_share_stop_server_shutdown_timeout": "Zatrzymaj udostępnianie (zostało {}s)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Czas upłynie za {}",
+ "gui_receive_start_server": "Rozpocznij tryb odbierania",
+ "gui_receive_stop_server": "Zatrzymaj tryb odbierania",
+ "gui_receive_stop_server_shutdown_timeout": "Zatrzymaj tryb odbierania (pozostało {}s)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Czas upływa za {}",
+ "gui_copy_url": "Kopiuj adres załącznika",
+ "gui_copy_hidservauth": "Kopiuj HidServAuth",
+ "gui_downloads": "Historia pobierania",
+ "gui_no_downloads": "Nie pobrano jeszcze niczego",
+ "gui_canceled": "Anulowano",
+ "gui_copied_url_title": "Skopiowano adres URL OnionShare",
+ "gui_copied_url": "Adres URL OnionShare został skopiowany do schowka",
+ "gui_copied_hidservauth_title": "Skopiowano HidServAuth",
+ "gui_copied_hidservauth": "Linijka HidServAuth została skopiowana do schowka",
+ "gui_please_wait": "Rozpoczynam... Kliknij, aby zatrzymać.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} upłynęło.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (obliczam)",
+ "gui_download_upload_progress_eta": "{0:s}, pozostało: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Nie tak szybko",
+ "gui_share_quit_warning": "Jesteś w trakcie wysyłania plików. Jesteś pewien, że chcesz wyjść z OnionShare?",
+ "gui_receive_quit_warning": "Odbierasz teraz pliki. Jesteś pewien, że chcesz wyjść z OnionShare?",
+ "gui_quit_warning_quit": "Wyjście",
+ "gui_quit_warning_dont_quit": "Anuluj",
+ "error_rate_limit": "Ktoś zbyt często próbował odczytać Twój adres, co może oznaczać, że ktoś próbuje go odgadnąć, zatem OnionShare zatrzymał serwer. Rozpocznij udostępnianie ponownie i wyślij odbiorcy nowy adres aby udostępniać Twoje pliki.",
+ "zip_progress_bar_format": "Kompresuję: %p%",
+ "error_stealth_not_supported": "Aby skorzystać z autoryzacji klienta wymagana jest wersja programu Tor 0.2.9.1-alpha lub nowsza, bądź Tor Browser w wersji 6.5 lub nowszej oraz python3-stem w wersji 1.5 lub nowszej.",
+ "error_ephemeral_not_supported": "OnionShare wymaga programu Tor w wersji 0.2.7.1 lub nowszej oraz python3-stem w wersji 1.4.0 lub nowszej.",
+ "gui_settings_window_title": "Ustawienia",
+ "gui_settings_whats_this": "<a href='{0:s}'>Co to jest?</a>",
+ "gui_settings_stealth_option": "Użyj autoryzacji klienta",
+ "gui_settings_stealth_hidservauth_string": "Skoro zapisałeś swój klucz prywatny do ponownego użycia, oznacza to, że możesz\nnacisnąć aby skopiować Twój HidServAuth.",
+ "gui_settings_autoupdate_label": "Sprawdź nową wersję",
+ "gui_settings_autoupdate_option": "Poinformuj mnie, kiedy nowa wersja programu będzie dostępna",
+ "gui_settings_autoupdate_timestamp": "Ostatnie sprawdzenie aktualizacji: {}",
+ "gui_settings_autoupdate_timestamp_never": "Nigdy",
+ "gui_settings_autoupdate_check_button": "Sprawdź, czy nowa wersja programu jest dostępna",
+ "gui_settings_general_label": "Ustawienia ogólne",
+ "gui_settings_sharing_label": "Ustawienia udostępniania",
+ "gui_settings_close_after_first_download_option": "Przestań udostępniać po pierwszym pobraniu",
+ "gui_settings_connection_type_label": "W jaki sposób OnionShare powinien połączyć się z siecią Tor?",
+ "gui_settings_connection_type_bundled_option": "Skorzystaj z wersji Tora udostępnionego wraz z OnionShare",
+ "gui_settings_connection_type_automatic_option": "Spróbuj automatycznej konfiguracji za pomocą Tor Browser",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "Połącz z użyciem pliku socket",
+ "gui_settings_connection_type_test_button": "Sprawdź połączenie z siecią Tor",
+ "gui_settings_control_port_label": "Port sterowania",
+ "gui_settings_socket_file_label": "Plik socket",
+ "gui_settings_socks_label": "Port SOCKS",
+ "gui_settings_authenticate_label": "Ustawienia autoryzacji sieci Tor",
+ "gui_settings_authenticate_no_auth_option": "Brak autoryzacji lub autoryzacji ciasteczek",
+ "gui_settings_authenticate_password_option": "Hasło",
+ "gui_settings_password_label": "Hasło",
+ "gui_settings_tor_bridges": "Wsparcie mostków sieci Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Nie korzystaj z mostków sieci Tor",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "Uwaga: Mostki meek_lite są bardzo kosztowne dla Tor Project.<br><br>Korzystaj z nich tylko wtedy, gdy nie możesz połączyć się bezpośrednio z siecią Tor, poprzez obsf4 albo przez inne normalne mostki.",
+ "gui_settings_tor_bridges_custom_radio_option": "Użyj niestandardowych mostków",
+ "gui_settings_tor_bridges_custom_label": "Mostki możesz znaleźć na <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Żadne z dodanych przez Ciebie mostków nie działają.\nZweryfikuj je lub dodaj inne.",
+ "gui_settings_button_save": "Zapisz",
+ "gui_settings_button_cancel": "Anuluj",
+ "gui_settings_button_help": "Pomoc",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "Nie można połączyć się z kontrolerem Tor, ponieważ Twoje ustawienia nie mają sensu.",
+ "settings_error_automatic": "Nie można połączyć się z kontrolerem Tor. Czy Tor Browser (dostępny na torproject.org) działa w tle?",
+ "settings_error_socket_port": "Nie można połączyć się z kontrolerem Tor pod adresem {}:{}.",
+ "settings_error_socket_file": "Nie można połączyć się z kontrolerem Tor używając pliku socket znajdującym się w ścieżce {}.",
+ "settings_error_auth": "Połączono z {}:{} ale nie można uwierzytelnić. Być może to nie jest kontroler Tor?",
+ "settings_error_missing_password": "Połączono z kontrolerem Tor ale wymaga on hasła do uwierzytelnienia.",
+ "settings_error_unreadable_cookie_file": "Połączono z kontrolerem Tor ale hasło może być niepoprawne albo Twój użytkownik nie ma uprawnień do odczytania plików cookie.",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "Połączenie się z siecią Tor zajmuje zbyt dużo czasu. Być może nie jesteś połączony z internetem albo masz niedokładny zegar systemowy?",
+ "settings_error_bundled_tor_broken": "OnionShare nie mógł połączyć się z siecią Tor w tle\n{}",
+ "settings_test_success": "Połączono z kontrolerem Tor.\n\nWersja Tor: {}\nWsparcie ulotnych serwisów onion: {}.\nWsparcie autoryzacji klienta: {}.\nWsparcie adresów onion nowej generacji: {}.",
+ "error_tor_protocol_error": "Pojawił się błąd z Tor: {}",
+ "error_tor_protocol_error_unknown": "Pojawił się nieznany błąd z Tor",
+ "error_invalid_private_key": "Ten typ klucza prywatnego jest niewspierany",
+ "connecting_to_tor": "Łączę z siecią Tor",
+ "update_available": "Nowa wersja programu OnionShare jest dostępna. <a href='{}'>Naciśnij tutaj</a> aby ją ściągnąć.<br><br>Korzystasz z wersji {} a najnowszą jest {}.",
+ "update_error_check_error": "Nie można sprawdzić czy są dostępne aktualizacje. Strona programu OnionShare mówi, że ostatnia wersja programu jest nierozpoznawalna '{}'…",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Tak",
+ "gui_tor_connection_ask_quit": "Wyjście",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "Udostępnianie",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "Otrzymuję",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Przeglądaj",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "Wyczyść wszystko",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "Preferowany język",
+ "gui_settings_language_changed_notice": "",
+ "timeout_upload_still_running": "Czekam na ukończenie wysyłania",
+ "gui_add_files": "Dodaj pliki",
+ "gui_add_folder": "Dodaj foldery"
+}
diff --git a/share/locale/pt.json b/share/locale/pt.json
deleted file mode 100644
index 71391957..00000000
--- a/share/locale/pt.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "connecting_ctrlport": "Conectando-se à porta de controle Tor para configurar serviço escondido na porta {0:d}.",
- "cant_connect_ctrlport": "Não pode conectar à porta de controle Tor na porta {0:s}. O Tor está rodando?",
- "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:",
- "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor",
- "not_a_file": "{0:s} não é um arquivo.",
- "gui_copied_url": "URL foi copiado para área de transferência",
- "download_page_loaded": "Página de download carregada",
- "other_page_loaded": "Outra página tem sido carregada"
-}
diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json
new file mode 100644
index 00000000..7ff64809
--- /dev/null
+++ b/share/locale/pt_BR.json
@@ -0,0 +1,212 @@
+{
+ "config_onion_service": "Configurando o serviço onion na porta {0:d}.",
+ "preparing_files": "Comprimindo arquivos.",
+ "give_this_url": "Dar este endereço ao destinatário:",
+ "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:",
+ "give_this_url_receive": "Enviar este endereço à pessoa remetente:",
+ "give_this_url_receive_stealth": "Dar este endereço e HidServAuth à pessoa remetente:",
+ "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor",
+ "not_a_file": "{0:s} não é um ficheiro válido.",
+ "not_a_readable_file": "{0:s} não é um ficheiro legível.",
+ "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion",
+ "other_page_loaded": "Endereço carregado",
+ "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático",
+ "closing_automatically": "Interrompido após o término da transferência",
+ "timeout_download_still_running": "Esperando que o download termine",
+ "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas",
+ "systray_menu_exit": "Sair",
+ "systray_download_started_title": "O download de OnionShare começou",
+ "systray_download_started_message": "Alguém começou fazer o download dos seus arquivos",
+ "systray_download_completed_title": "O download de OnionShare terminou",
+ "systray_download_completed_message": "Essa pessoa terminou de fazer o download dos seus arquivos",
+ "systray_download_canceled_title": "O download de OnionShare foi cancelado",
+ "systray_download_canceled_message": "Essa pessoa cancelou o download",
+ "systray_upload_started_title": "OnionShare começou a carregar",
+ "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador",
+ "help_local_only": "Não use Tor (unicamente para programação)",
+ "help_stay_open": "Continuar a compartilhar após o envio de documentos",
+ "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos",
+ "help_stealth": "Usar autorização de cliente (avançado)",
+ "help_receive": "Receber compartilhamentos ao invés de enviá-los",
+ "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco",
+ "help_filename": "Lista de arquivos ou pastas a compartilhar",
+ "help_config": "Personalizar a configuração JSON de localização de arquivos (opcional)",
+ "gui_drag_and_drop": "Arrastar arquivos e pastas\npara começar a compartilhá-los",
+ "gui_add": "Adicionar",
+ "gui_delete": "Apagar",
+ "gui_choose_items": "Escolher",
+ "gui_share_start_server": "Começar a compartilhar",
+ "gui_share_stop_server": "Parar de compartilhar",
+ "gui_share_stop_server_shutdown_timeout": "Parar de compartilhar ({}segundos para terminar)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às",
+ "gui_receive_start_server": "Modo Começar a Receber",
+ "gui_receive_stop_server": "Modo Parar de Receber",
+ "gui_receive_stop_server_shutdown_timeout": "Modo Parar de Receber ({}segundos para terminar)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às {}",
+ "gui_copy_url": "Copiar endereço",
+ "gui_copy_hidservauth": "Copiar HidServAuth",
+ "gui_downloads": "Histórico de download",
+ "gui_no_downloads": "Nenhum download por enquanto",
+ "gui_canceled": "Cancelado",
+ "gui_copied_url_title": "Endereço OnionShare copiado",
+ "gui_copied_url": "URL foi copiado na área de transferência",
+ "gui_copied_hidservauth_title": "HidServAuth copiado",
+ "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência",
+ "gui_please_wait": "Começando...Clique para cancelar.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)",
+ "gui_download_upload_progress_eta": "{0:s}, tempo estimado para término: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Mais devagar",
+ "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?",
+ "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?",
+ "gui_quit_warning_quit": "Sair",
+ "gui_quit_warning_dont_quit": "Cancelar",
+ "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.",
+ "zip_progress_bar_format": "Comprimindo: %p%",
+ "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.",
+ "gui_settings_window_title": "Configurações",
+ "gui_settings_whats_this": "<a href='{0:s}'>O que é isso?</a>",
+ "gui_settings_stealth_option": "Usar autorização de cliente",
+ "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode clicar para copiar o seu HidServAuth.",
+ "gui_settings_autoupdate_label": "Procurar a nova versão",
+ "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível",
+ "gui_settings_autoupdate_timestamp": "Última atualização: {}",
+ "gui_settings_autoupdate_timestamp_never": "Nunca",
+ "gui_settings_autoupdate_check_button": "Procurar a nova versão",
+ "gui_settings_general_label": "Configurações gerais",
+ "gui_settings_sharing_label": "Compartilhando configurações",
+ "gui_settings_close_after_first_download_option": "Parar de compartilhar após o envio dos arquivos",
+ "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?",
+ "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare",
+ "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor",
+ "gui_settings_connection_type_control_port_option": "Conectar usando entrada de controle",
+ "gui_settings_connection_type_socket_file_option": "Conectar usando um ficheiro socket",
+ "gui_settings_connection_type_test_button": "Testar a conexão a Tor",
+ "gui_settings_control_port_label": "Entrada de controle",
+ "gui_settings_socket_file_label": "Ficheiro socket",
+ "gui_settings_socks_label": "Entrada SOCKS",
+ "gui_settings_authenticate_label": "Configurações de autenticação do Tor",
+ "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação",
+ "gui_settings_authenticate_password_option": "Palavra-passe",
+ "gui_settings_password_label": "Palavra-passe",
+ "gui_settings_tor_bridges": "Ajuda para pontes Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.<br><br>Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.",
+ "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas",
+ "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.",
+ "gui_settings_button_save": "Salvar",
+ "gui_settings_button_cancel": "Cancelar",
+ "gui_settings_button_help": "Ajuda",
+ "gui_settings_shutdown_timeout_checkbox": "Usar cronômetro para encerrar automaticamente",
+ "gui_settings_shutdown_timeout": "Encerrar o compartilhamento às:",
+ "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.",
+ "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?",
+ "settings_error_socket_port": "Não pode ligar ao controlador do Tor em {}:{}.",
+ "settings_error_socket_file": "Não foi possível conectar ao controlador Tor usando o arquivo de socket {}.",
+ "settings_error_auth": "Conectado a {}:{}, mas não foi possível autenticar. Talvez este não seja um controlador Tor?",
+ "settings_error_missing_password": "Conectado ao controlador Tor, mas é preciso ter uma senha para autenticar.",
+ "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.",
+ "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.",
+ "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?",
+ "settings_error_bundled_tor_broken": "OnionShare não pôde se conectar a Tor em segundo plano:\n{}",
+ "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.",
+ "error_tor_protocol_error": "Houve um erro com Tor: {}",
+ "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor",
+ "error_invalid_private_key": "Este tipo de chave privada não possui suporte",
+ "connecting_to_tor": "Conectando à rede Tor",
+ "update_available": "Atualização de OnionShare disponível. <a href='{}'>Clique aqui</a> para obtê-la.<br><br>Você está usando a versão {} e a última é {}.",
+ "update_error_check_error": "Não foi possível verificar por novas versões: o website do OnionShare está dizendo que a última versão é a irreconhecível '{}'…",
+ "update_error_invalid_latest_version": "Não foi possível verificar por uma nova versão: talvez você não esteja conectada ao Tor, ou talvez o website do OnionShare esteja fora do ar?",
+ "update_not_available": "Você está rodando a última versão de OnionShare.",
+ "gui_tor_connection_ask": "Abrir as configurações para consertar a conexão ao Tor?",
+ "gui_tor_connection_ask_open_settings": "Sim",
+ "gui_tor_connection_ask_quit": "Sair",
+ "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.",
+ "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.",
+ "gui_tor_connection_lost": "Desconectado do Tor.",
+ "gui_server_started_after_timeout": "O tempo esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.",
+ "gui_server_timeout_expired": "O temporizador já esgotou.\nPor favor, atualize-o antes de começar a compartilhar.",
+ "share_via_onionshare": "Compartilhar usando OnionShare",
+ "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo",
+ "gui_save_private_key_checkbox": "Usar o mesmo endereço",
+ "gui_share_url_description": "<b>Qualquer pessoa</b> com este endereço do OnionShare pode <b>baixar</b> seus arquivos usando o <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Qualquer pessoa</b> com este endereço do OnionShare pode <b>carregar</b> arquivos no seu computador usando o <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.<br><br>Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desative a opção \"Usar o mesmo endereço\" nas configurações.)",
+ "gui_url_label_stay_open": "Este compartilhamento não sera encerrado automaticamente.",
+ "gui_url_label_onetime": "Este compartilhamento será encerrado após completar uma vez.",
+ "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.<br><br>Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desative a opção \"Usar o mesmo endereço\" nas configurações.)",
+ "gui_status_indicator_share_stopped": "Pronto para compartilhar",
+ "gui_status_indicator_share_working": "Começando…",
+ "gui_status_indicator_share_started": "Compartilhando",
+ "gui_status_indicator_receive_stopped": "Pronto para receber",
+ "gui_status_indicator_receive_working": "Começando…",
+ "gui_status_indicator_receive_started": "Recebendo",
+ "gui_file_info": "{} arquivos, {}",
+ "gui_file_info_single": "{} arquivo, {}",
+ "history_in_progress_tooltip": "{} em curso",
+ "history_completed_tooltip": "{} completado",
+ "info_in_progress_uploads_tooltip": "{} upload(s) em progresso",
+ "info_completed_uploads_tooltip": "{} upload(s) completado(s)",
+ "error_cannot_create_downloads_dir": "Não foi possível a pasta do modo de recepção: {}",
+ "receive_mode_downloads_dir": "Os arquivos enviados para você aparecem na seguinte pasta: {}",
+ "receive_mode_warning": "Atenção: O modo de recepção permite que as pessoas enviem arquivos para o seu computador. Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.",
+ "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.<br><br><b>Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.</b>",
+ "receive_mode_upload_starting": "Um upload de tamanho total {} está sendo iniciado",
+ "receive_mode_received_file": "Recebido: {}",
+ "gui_mode_share_button": "Compartilhar Arquivos",
+ "gui_mode_receive_button": "Receber Arquivos",
+ "gui_settings_receiving_label": "Configurações de recepção",
+ "gui_settings_downloads_label": "Armazenar arquivos em",
+ "gui_settings_downloads_button": "Navegar",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "Modo público",
+ "systray_close_server_title": "Servidor OnionShare encerrado",
+ "systray_close_server_message": "Um usuário encerrou o servidor",
+ "systray_page_loaded_title": "Página Carregada",
+ "systray_download_page_loaded_message": "Um usuário carregou a página de download",
+ "systray_upload_page_loaded_message": "Um usuário carregou a página de upload",
+ "gui_uploads": "Histórico de Uploads",
+ "gui_no_uploads": "Nenhum upload realizado",
+ "gui_clear_history": "Limpar Tudo",
+ "gui_upload_in_progress": "Upload Iniciado {}",
+ "gui_upload_finished_range": "Upload de {} feito para {}",
+ "gui_upload_finished": "Upload realizado de {}",
+ "gui_download_in_progress": "Download Iniciado {}",
+ "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}",
+ "gui_settings_language_label": "Idioma",
+ "gui_settings_language_changed_notice": "Reinicie OnionShare para que sua alteração de idioma tenha efeito.",
+ "timeout_upload_still_running": "Esperando o término do upload",
+ "gui_add_files": "Adicionar Documentos",
+ "gui_add_folder": "Adicionar Pasta",
+ "gui_share_mode_no_files": "Nenhum arquivo ainda enviado",
+ "gui_connect_to_tor_for_onion_settings": "Conectar ao Tor para ver as configurações do serviço onion",
+ "error_cannot_create_data_dir": "Pasta de dados OnionShare não foi criada: {}",
+ "receive_mode_data_dir": "Os arquivos que lhe foram enviados estão nesta pasta: {}",
+ "gui_settings_data_dir_label": "Salvar arquivos em",
+ "gui_settings_data_dir_browse_button": "Navegar",
+ "systray_share_started_title": "O compartilhamento iniciou",
+ "systray_share_started_message": "Iniciando o envio de arquivos",
+ "systray_share_completed_title": "O compartilhamento completou-se",
+ "systray_share_completed_message": "O envio de arquivos terminou",
+ "systray_share_canceled_title": "O compartilhamento foi anulado",
+ "systray_share_canceled_message": "Alguém cancelou o recebimento dos seus arquivos",
+ "systray_receive_started_title": "O recebimento iniciou",
+ "systray_receive_started_message": "Alguém está lhe enviando arquivos",
+ "gui_all_modes_history": "Histórico",
+ "gui_all_modes_clear_history": "Apagar Tudo",
+ "gui_all_modes_transfer_started": "Iniciou {}",
+ "gui_all_modes_transfer_finished_range": "Transferido {} - {}",
+ "gui_all_modes_transfer_finished": "Transferido {}",
+ "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}",
+ "gui_all_modes_transfer_canceled": "Cancelado {}",
+ "gui_share_mode_timeout_waiting": "Esperando para completar o envio",
+ "gui_receive_mode_no_files": "Nenhum arquivo recebido",
+ "gui_receive_mode_timeout_waiting": "Esperando para completar o recebimento"
+}
diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json
new file mode 100644
index 00000000..7085f2c2
--- /dev/null
+++ b/share/locale/pt_PT.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor",
+ "not_a_file": "{0:s} não é um arquivo.",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "Outra página tem sido carregada",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Sair",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "Eliminar",
+ "gui_choose_items": "Escolha",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "Cancelada",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "URL foi copiado para área de transferência",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Sair",
+ "gui_quit_warning_dont_quit": "Cancelar",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "Definições",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Nunca",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Definições gerais",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "Password",
+ "gui_settings_password_label": "Password",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "Cancelar",
+ "gui_settings_button_help": "Ajuda",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Sim",
+ "gui_tor_connection_ask_quit": "Sair",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Navegar",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/ro.json b/share/locale/ro.json
new file mode 100644
index 00000000..54750c24
--- /dev/null
+++ b/share/locale/ro.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Închidere",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "Şterge",
+ "gui_choose_items": "Alegeți",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "Anulat",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Închidere",
+ "gui_quit_warning_dont_quit": "Anulare",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "Setari",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Niciodata",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Setări generale",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "Port de control",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "Parolă",
+ "gui_settings_password_label": "Parolă",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "Salvare",
+ "gui_settings_button_cancel": "Anulare",
+ "gui_settings_button_help": "Ajutor",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Da",
+ "gui_tor_connection_ask_quit": "Închidere",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "Impartasirea creatiilor",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "Primire",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Răsfoiește",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/ru.json b/share/locale/ru.json
index 193c158d..de372fe6 100644
--- a/share/locale/ru.json
+++ b/share/locale/ru.json
@@ -1,11 +1,216 @@
{
- "connecting_ctrlport": "Соединяемся с контрольным портом Tor для создания скрытого сервиса на порту {0:d}.",
- "cant_connect_ctrlport": "Невозможно соединиться с контрольным портом Tor на порту {0:s}. Tor запущен?",
- "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:",
- "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер",
- "not_a_file": "{0:s} не является файлом.",
- "gui_copied_url": "Ссылка скопирована в буфер обмена",
- "download_page_loaded": "Страница закачки загружена",
- "other_page_loaded": "Другая страница была загружена",
- "gui_copy_url": "Скопировать ссылку"
+ "give_this_url": "Передайте получателю этот адрес:",
+ "ctrlc_to_stop": "Нажмите Ctrl+C, чтобы остановить сервер",
+ "not_a_file": "{0:s} недопустимый файл.",
+ "gui_copied_url": "Ссылка OnionShare скопирована в буфер обмена",
+ "other_page_loaded": "Адрес загружен",
+ "gui_copy_url": "Копировать адрес",
+ "systray_menu_exit": "Выйти",
+ "gui_add": "Добавить",
+ "gui_delete": "Удалить",
+ "gui_choose_items": "Выбрать",
+ "gui_canceled": "Отменена",
+ "gui_quit_warning_quit": "Выход",
+ "gui_quit_warning_dont_quit": "Отмена",
+ "gui_settings_window_title": "Настройки",
+ "gui_settings_autoupdate_timestamp_never": "Никогда",
+ "gui_settings_general_label": "Общие настройки",
+ "gui_settings_control_port_label": "Контрольный порт",
+ "gui_settings_authenticate_password_option": "Пароль",
+ "gui_settings_password_label": "Пароль",
+ "gui_settings_button_save": "Сохранить",
+ "gui_settings_button_cancel": "Отмена",
+ "gui_settings_button_help": "Помощь",
+ "gui_tor_connection_ask_open_settings": "Да",
+ "gui_tor_connection_ask_quit": "Выйти",
+ "gui_status_indicator_share_started": "Идёт отправка",
+ "gui_status_indicator_receive_started": "Идёт получение",
+ "gui_settings_downloads_label": "Путь сохранения файлов: ",
+ "gui_settings_downloads_button": "Выбрать",
+ "gui_clear_history": "Очистить Все",
+ "gui_settings_language_label": "Язык интерфейса",
+ "config_onion_service": "Назначем \"луковому\" сервису порт {:d}.",
+ "preparing_files": "Сжимаем файлы.",
+ "give_this_url_stealth": "Передайте этот адрес и строку HidServAuth получателю:",
+ "give_this_url_receive": "Передайте этот адрес отправителю:",
+ "give_this_url_receive_stealth": "Передайте этот адрес и строку HidServAuth отправителю:",
+ "not_a_readable_file": "{0:s} не читаемый файл.",
+ "no_available_port": "Не удалось найти доступный порт для запуска \"лукового\" сервиса",
+ "close_on_timeout": "Время ожидания таймера истекло, сервис остановлен",
+ "closing_automatically": "Загрузка завершена, сервис остановлен",
+ "timeout_download_still_running": "Ожидаем завершения скачивания",
+ "timeout_upload_still_running": "Ожидаем завершения загрузки",
+ "large_filesize": "Внимание: Отправка данных большого объёма может занять продолжительное время (несколько часов)",
+ "systray_download_started_title": "OnionShare: скачивание началось",
+ "systray_download_started_message": "Пользователь начал загружать Ваши файлы",
+ "systray_download_completed_title": "OnionShare: скачивание завершено",
+ "systray_download_completed_message": "Пользователь завершил скачивание Ваших файлов",
+ "systray_download_canceled_title": "OnionShare: скачивание отменено",
+ "systray_download_canceled_message": "Пользователь отменил скачивание",
+ "systray_upload_started_title": "OnionShare: загрузка началась",
+ "systray_upload_started_message": "Пользователь начал загрузку файлов на Ваш компьютер",
+ "help_local_only": "Не использовать Tor (только для разработки)",
+ "help_stay_open": "Продолжить отправку после первого скачивания",
+ "help_shutdown_timeout": "Остановить отправку после заданного количества секунд",
+ "help_stealth": "Использовать авторизацию клиента (дополнительно)",
+ "help_receive": "Получать загрузки вместо их отправки",
+ "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск",
+ "help_filename": "Список файлов или папок для отправки",
+ "help_config": "Расположение пользовательского конфигурационного JSON-файла (необязательно)",
+ "gui_drag_and_drop": "Перетащите сюда файлы и/или папки,\nкоторые хотите отправить.",
+ "gui_share_start_server": "Начать отправку",
+ "gui_share_stop_server": "Закончить отправку",
+ "gui_share_stop_server_shutdown_timeout": "Остановить отправку (осталось {}с)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}",
+ "gui_receive_start_server": "Включить режим получения",
+ "gui_receive_stop_server": "Выключить режим получения",
+ "gui_receive_stop_server_shutdown_timeout": "Выключить режим получения (осталось {}с)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}",
+ "gui_copy_hidservauth": "Скопировать строку HidServAuth",
+ "gui_downloads": "История скачиваний",
+ "gui_no_downloads": "Скачиваний пока нет ",
+ "gui_copied_url_title": "Адрес OnionShare скопирован",
+ "gui_copied_hidservauth_title": "Строка HidServAuth скопирована",
+ "gui_copied_hidservauth": "Строка HidServAuth скопирована в буфер обмена",
+ "gui_please_wait": "Запуск... Для отмены нажмите здесь.",
+ "gui_download_upload_progress_complete": "%p%, прошло {0:s}.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (вычисляем)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Не так быстро",
+ "gui_share_quit_warning": "Идёт процесс отправки файлов. Уверены, что хотите завершить работу OnionShare?",
+ "gui_receive_quit_warning": "Идёт процесс получения файлов. Уверены, что хотите завершить работу OnionShare?",
+ "error_rate_limit": "Кто-то совершил слишком много попыток подключения к Вашему серверу отправки файлов. Возможно, его пытаются вычислить. OnionShare остановил сервер. Отправьте Ваши данные повторно и перешлите получателю новый адрес.",
+ "zip_progress_bar_format": "Сжатие: %p%",
+ "error_stealth_not_supported": "Для использования авторизации клиента необходимы как минимум версии Tor 0.2.9.1-alpha (или Tor Browser 6.5) и библиотеки python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "Для работы OnionShare необходимы как минимум версии Tor 0.2.7.1 и библиотеки python3-stem 1.4.0.",
+ "gui_settings_whats_this": "<a href='{0:s}'>Что это?</a>",
+ "gui_settings_stealth_option": "Использовать авторизацию клиента",
+ "gui_settings_stealth_hidservauth_string": "Сохранили Ваш приватный ключ для повторного использования.\nНажмите сюда, чтобы скопировать строку HidServAuth.",
+ "gui_settings_autoupdate_label": "Проверить наличие новой версии",
+ "gui_settings_autoupdate_option": "Уведомить меня, когда будет доступна новая версия",
+ "gui_settings_autoupdate_timestamp": "Последняя проверка: {}",
+ "gui_settings_autoupdate_check_button": "Проверить наличие новой версии",
+ "gui_settings_sharing_label": "Настройки отправки",
+ "gui_settings_close_after_first_download_option": "Завершить отправку Ваших файлов\nпосле их первого скачивания",
+ "gui_settings_connection_type_label": "Как OnionShare следует подключаться к сети Tor?",
+ "gui_settings_connection_type_bundled_option": "Использовать версию Tor, встроенную в OnionShare",
+ "gui_settings_connection_type_automatic_option": "Автоматическая настройка при помощи Tor Browser",
+ "gui_settings_connection_type_control_port_option": "Использовать контрольный порт",
+ "gui_settings_connection_type_socket_file_option": "Использовать файл сокет",
+ "gui_settings_connection_type_test_button": "Проверить подключение к сети Tor",
+ "gui_settings_socket_file_label": "Файл сокет",
+ "gui_settings_socks_label": "Порт SOCKS",
+ "gui_settings_authenticate_label": "Настройки аутентификации Tor",
+ "gui_settings_authenticate_no_auth_option": "Без аутентификации или cookie-аутентификации",
+ "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Не использовать \"мосты\"",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные подключаемые транспорты obfs4",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные подключаемые транспорты obfs4 (необходим obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные транспорты meek_lite (Azure)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные транспорты meek_lite (Azure) (необходим obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project.<br><br>Используйте их только если не можете подключиться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".",
+ "gui_settings_tor_bridges_custom_radio_option": "Использовать пользовательские \"мосты\"",
+ "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь: <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работает.\nПроверьте их снова или добавьте другие.",
+ "gui_settings_shutdown_timeout_checkbox": "Использовать таймер",
+ "gui_settings_shutdown_timeout": "Остановить загрузку в:",
+ "settings_error_unknown": "Невозможно произвести подключение к контроллеру Tor: некорректные настройки.",
+ "settings_error_automatic": "Ошибка подключения к контроллеру Tor. Запущен ли Tor Browser (torproject.org) в фоновом режиме?",
+ "settings_error_socket_port": "Ошибка подключения к контроллеру Tor в {}:{}.",
+ "settings_error_socket_file": "Ошибка подключения к контроллеру Tor с использованием файла-сокета {}.",
+ "settings_error_auth": "Подключено к {}:{}, ошибка проверки подлинности. Возможно, это не контроллер Tor?",
+ "settings_error_missing_password": "Подключено к контроллеру Tor, но для аутентификации нужен пароль.",
+ "settings_error_unreadable_cookie_file": "Подключено к контроллеру Tor, но возможна ошибка пароля, или пользователю запрещено чтение файла cookie.",
+ "settings_error_bundled_tor_not_supported": "Версия Tor, которая поставляется вместе с OnionShare, не подходит для разработчика в Windows и macOS.",
+ "settings_error_bundled_tor_timeout": "Подключение к Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет, или у вас неточно настроено системное время?",
+ "settings_error_bundled_tor_broken": "Ошибка подключения OnionShare к Tor в фоновом режиме:\n{}",
+ "settings_test_success": "Подключено к контроллеру Tor.\n\nВерсия Tor: {}\nПоддержка временных \"луковых\" сервисов: {}.\nПоддержка аутентификации клиента: {}.\nПоддержка адресов .onion следующего поколения: {}.",
+ "error_tor_protocol_error": "Ошибка Tor: {}",
+ "error_tor_protocol_error_unknown": "Неизвестная ошибка Tor",
+ "error_invalid_private_key": "Этот приватный ключ не поддерживается",
+ "connecting_to_tor": "Подключение к сети Tor",
+ "update_available": "Вышла новая версия OnionShare. Для загрузки <a href='{}'>нажмите сюда</a>.<br><br>Вы используется версию {}, наиболее свежая версия {}.",
+ "update_error_check_error": "Ошибка проверки новых версий: сайт OnionShare сообщает, что не удалось распознать наиболее свежую версию '{}'…",
+ "update_error_invalid_latest_version": "Ошибка проверки новой версии: возможно, вы не подключены к Tor, или сайт OnionShare не работает?",
+ "update_not_available": "Вы используете наиболее свежую версию OnionShare.",
+ "gui_tor_connection_ask": "Перейти в раздел \"Настройки\" для решения проблем с подключением к Tor?",
+ "gui_tor_connection_error_settings": "Попробуйте изменить способ подключения OnionShare к сети Tor в разделе \"Настройки\".",
+ "gui_tor_connection_canceled": "Ошибка подключения к Tor.\n\nПожалуйста, убедитесь что подключены к сети Интернет. Откройте OnionShare снова и настройте подключение к Tor.",
+ "gui_tor_connection_lost": "Отключено от Tor.",
+ "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, отправьте файлы заново.",
+ "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для начала отправки.",
+ "share_via_onionshare": "OnionShare это",
+ "gui_use_legacy_v2_onions_checkbox": "Используйте устаревшие адреса",
+ "gui_save_private_key_checkbox": "Используйте постоянный адрес",
+ "gui_share_url_description": "<b>Кто угодно</b> c этим адресом OnionShare может <b>скачать</b> Ваши файлы при помощи <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Кто угодно</b> c этим адресом OnionShare может <b>загрузить</b> файлы на ваш компьютер с помощью<b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Эта отправка не будет завершена автоматически.<br><br>Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)",
+ "gui_url_label_stay_open": "Эта отправка не будет остановлена автоматически.",
+ "gui_url_label_onetime": "Эта отправка будет завершена автоматически после первой загрузки.",
+ "gui_url_label_onetime_and_persistent": "Эта отправка не будет завершена автоматически.<br><br>Каждая последующая отправка будет повторно использовать этот адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)",
+ "gui_status_indicator_share_stopped": "Данные готовы к отправке",
+ "gui_status_indicator_share_working": "Ожидайте…",
+ "gui_status_indicator_receive_stopped": "Данные готовы к получению",
+ "gui_status_indicator_receive_working": "Ожидайте…",
+ "gui_file_info": "{} файлы, {}",
+ "gui_file_info_single": "{} файл, {}",
+ "history_in_progress_tooltip": "{} в ходе выполнения",
+ "history_completed_tooltip": "{} завершено",
+ "info_in_progress_uploads_tooltip": "{} загрузка(и) в ходе выполнения",
+ "info_completed_uploads_tooltip": "{} загрузка(и) завершена(ы)",
+ "error_cannot_create_downloads_dir": "Не удалось создать папку в режиме получения: {}",
+ "receive_mode_downloads_dir": "Загруженные Вас файлы находятся в папке: {}",
+ "receive_mode_warning": "Внимание: режим получения позволяет другим людям загружать файлы на ваш компьютер. Некоторые файлы могут представлять угрозу для вашего компьютера. Открывайте файлы от тех людей, которым вы доверяете, или если вы точно знаете, что делаете.",
+ "gui_receive_mode_warning": "Режим получения файлов позволяет другим людям загружать файлы на ваш компьютер. <br><br><b>Некоторые файлы могут представлять угрозу для вашего компьютера. Открывайте файлы от тех людей, которым доверяете, или если вы точно знаете, что делаете.</b>",
+ "receive_mode_upload_starting": "Начинается загрузка общим объёмом {}",
+ "receive_mode_received_file": "Получено: {}",
+ "gui_mode_share_button": "Отправка файлов",
+ "gui_mode_receive_button": "Получение файлов",
+ "gui_settings_receiving_label": "Настройки получения",
+ "gui_settings_public_mode_checkbox": "Публичный режим",
+ "systray_close_server_title": "Сервер OnionShare отключен",
+ "systray_close_server_message": "Пользователь отключил сервер",
+ "systray_page_loaded_title": "Страница загружена",
+ "systray_download_page_loaded_message": "Пользователь находится на странице скачивания",
+ "systray_upload_page_loaded_message": "Пользователь посетил странцу загрузки",
+ "gui_uploads": "История загрузок",
+ "gui_no_uploads": "Загрузок пока нет",
+ "gui_upload_in_progress": "Загрузка началась {}",
+ "gui_upload_finished_range": "Загружено {} в {}",
+ "gui_upload_finished": "Загружено {}",
+ "gui_download_in_progress": "Загрузка началась {}",
+ "gui_open_folder_error_nautilus": "Не удаётся открыть папку без файлового менеджера Nautilus. Файл находится здесь: {}",
+ "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу.",
+ "gui_add_files": "Добавить файлы",
+ "gui_add_folder": "Добавить папку",
+ "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}",
+ "gui_settings_onion_label": "Настройки \"лукового\" сервиса",
+ "gui_connect_to_tor_for_onion_settings": "Подключитесь к Tor, чтобы видеть настройки \"лукового\" сервиса",
+ "receive_mode_data_dir": "Отправленные Вам файлы можно найти в этой папке: {}",
+ "gui_settings_data_dir_label": "Сохранять файлы в",
+ "gui_settings_data_dir_browse_button": "Выбрать",
+ "systray_page_loaded_message": "Адрес OnionShare загружен",
+ "systray_share_started_title": "Отправка Началась",
+ "systray_share_started_message": "Началась отправка файлов",
+ "systray_share_completed_title": "Отправка завершена",
+ "systray_share_completed_message": "Завершена отправка файлов",
+ "systray_share_canceled_title": "Отправка отменена",
+ "systray_share_canceled_message": "Кто-то отменил получение Ваших файлов",
+ "systray_receive_started_title": "Загрузка началась",
+ "systray_receive_started_message": "Кто-то отправляет Вам файлы",
+ "gui_all_modes_history": "История",
+ "gui_all_modes_clear_history": "Очистить историю полностью",
+ "gui_all_modes_transfer_started": "Начато {}",
+ "gui_all_modes_transfer_finished_range": "Отправлено {} - {}",
+ "gui_all_modes_transfer_finished": "Отправлено {}",
+ "gui_all_modes_transfer_canceled_range": "Отменено {} - {}",
+ "gui_all_modes_transfer_canceled": "Отменено {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s.} прошло.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (вычисляем)",
+ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_share_mode_no_files": "Пока нет отправленных файлов",
+ "gui_share_mode_timeout_waiting": "Ожидается завершение отправки",
+ "gui_receive_mode_no_files": "Пока нет полученных файлов",
+ "gui_receive_mode_timeout_waiting": "Ожидается завершение загрузки"
}
diff --git a/share/locale/sl.json b/share/locale/sl.json
new file mode 100644
index 00000000..42892ef9
--- /dev/null
+++ b/share/locale/sl.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "Izhod",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "Izberi",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "Odpovedan",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "Izhod",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "Nikoli",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "Splošne nastavitve",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "Krmilna vrata",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "Pomoč",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "Da",
+ "gui_tor_connection_ask_quit": "Izhod",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "Brskanje",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/sn.json b/share/locale/sn.json
new file mode 100644
index 00000000..11c255f6
--- /dev/null
+++ b/share/locale/sn.json
@@ -0,0 +1,188 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_add_files": "",
+ "gui_add_folder": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_connect_to_tor_for_onion_settings": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/sv.json b/share/locale/sv.json
new file mode 100644
index 00000000..f1072332
--- /dev/null
+++ b/share/locale/sv.json
@@ -0,0 +1,215 @@
+{
+ "config_onion_service": "Förbereder onion-tjänsten på port {0:d}.",
+ "preparing_files": "Komprimera filer.",
+ "give_this_url": "Ge den här adressen till mottagaren:",
+ "give_this_url_stealth": "Ge den här adressen och HidServAuth-raden till mottagaren:",
+ "give_this_url_receive": "Ge denna adress till avsändaren:",
+ "give_this_url_receive_stealth": "Ge denna adress och HidServAuth till avsändaren:",
+ "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern",
+ "not_a_file": "{0:s} är inte en giltig fil.",
+ "not_a_readable_file": "{0:s} är inte en läsbar fil.",
+ "no_available_port": "Kunde inte hitta en ledig kort för att börja onion-tjänsten",
+ "other_page_loaded": "Adress laddad",
+ "close_on_timeout": "Stoppad för att automatiska stopp-timern tiden tog slut",
+ "closing_automatically": "Stoppad för att hämtningen är klar",
+ "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar",
+ "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar",
+ "large_filesize": "Varning: Att skicka en stor fil kan ta timmar",
+ "systray_menu_exit": "Avsluta",
+ "systray_download_started_title": "OnionShare Nedladdning Startad",
+ "systray_download_started_message": "En användare började ladda ner dina filer",
+ "systray_download_completed_title": "OnionShare Nedladdning Klar",
+ "systray_download_completed_message": "Användaren har laddat ner dina filer",
+ "systray_download_canceled_title": "OnionShare Nedladdning Avbruten",
+ "systray_download_canceled_message": "Användaren avbröt nedladdningen",
+ "systray_upload_started_title": "OnionShare Uppladdning Påbörjad",
+ "systray_upload_started_message": "En användare började ladda upp filer på din dator",
+ "help_local_only": "Använd inte Tor (endast för utveckling)",
+ "help_stay_open": "Fortsätt dela efter att filer har skickats",
+ "help_shutdown_timeout": "Avbryt delning efter ett bestämt antal sekunder",
+ "help_stealth": "Använd klient-auktorisering (avancerat)",
+ "help_receive": "Ta emot delningar istället för att skicka dem",
+ "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken",
+ "help_filename": "Lista filer och mappar att dela",
+ "help_config": "Egenvald sökväg för JSON konfigurationsfil (valfri)",
+ "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att börja delning",
+ "gui_add": "Lägg till",
+ "gui_delete": "Radera",
+ "gui_choose_items": "Välj",
+ "gui_share_start_server": "Börja dela",
+ "gui_share_stop_server": "Avbryt delning",
+ "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern avslutar vid {}",
+ "gui_receive_start_server": "Börja mottagarläge",
+ "gui_receive_stop_server": "Avsluta Mottagarläge",
+ "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer avslutas kl {}",
+ "gui_copy_url": "Kopiera Adress",
+ "gui_copy_hidservauth": "Kopiera HidServAuth",
+ "gui_downloads": "Nedladdningshistorik",
+ "gui_no_downloads": "Inga Nedladdningar Än",
+ "gui_canceled": "Avbruten",
+ "gui_copied_url_title": "OnionShare-adress kopierad",
+ "gui_copied_url": "OnionShare-adress kopierad till urklipp",
+ "gui_copied_hidservauth_title": "HidServAuth Kopierad",
+ "gui_copied_hidservauth": "HidServAuth-rad kopierad till urklipp",
+ "gui_please_wait": "Börjar... klicka för att avbryta.",
+ "gui_download_upload_progress_complete": "%p%, {0:s} förflutit.",
+ "gui_download_upload_progress_starting": "{0:s}, %p% (beräknar)",
+ "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "version_string": "OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "Inte så fort",
+ "gui_share_quit_warning": "Du håller på att skicka filer. Är du säker på att du vill avsluta OnionShare?",
+ "gui_receive_quit_warning": "Du håller på att ta emot filer. Är du säker på att du vill avsluta OnionShare?",
+ "gui_quit_warning_quit": "Avsluta",
+ "gui_quit_warning_dont_quit": "Avbryt",
+ "error_rate_limit": "Någon har gjort för många felaktiga försök på din adress, vilket innebär att de kan försöka gissa det, så OnionShare har stoppat servern. Börja dela igen och skicka mottagaren en ny adress att dela.",
+ "zip_progress_bar_format": "Komprimerar: %p%",
+ "error_stealth_not_supported": "För att använda klientauktorisering behöver du minst både Tor 0.2.9.1-alpha (eller Tor Browser 6.5) och python3-stem 1.5.0.",
+ "error_ephemeral_not_supported": "OnionShare kräver minst både Tor 0.2.7.1 och python3-stem 1.4.0.",
+ "gui_settings_window_title": "Inställningar",
+ "gui_settings_whats_this": "<a href='{0:s}'>Vad är det här?</a>",
+ "gui_settings_stealth_option": "Använd klientauktorisering",
+ "gui_settings_stealth_hidservauth_string": "Efter att ha sparat din privata nyckel för återanvändning, innebär att du kan nu\nklicka för att kopiera din HidServAuth.",
+ "gui_settings_autoupdate_label": "Sök efter ny version",
+ "gui_settings_autoupdate_option": "Meddela mig när en ny version är tillgänglig",
+ "gui_settings_autoupdate_timestamp": "Senast kontrollerad: {}",
+ "gui_settings_autoupdate_timestamp_never": "Aldrig",
+ "gui_settings_autoupdate_check_button": "Sök efter ny version",
+ "gui_settings_general_label": "Allmänna inställningar",
+ "gui_settings_sharing_label": "Delningsinställningar",
+ "gui_settings_close_after_first_download_option": "Fortsätt dela efter att filer har skickats",
+ "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?",
+ "gui_settings_connection_type_bundled_option": "Använd Tor-versionen som är inbyggd i OnionShare",
+ "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser",
+ "gui_settings_connection_type_control_port_option": "Anslut med kontrollport",
+ "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen",
+ "gui_settings_connection_type_test_button": "Provningsanslutning till Tor",
+ "gui_settings_control_port_label": "Kontrollport",
+ "gui_settings_socket_file_label": "Socket-fil",
+ "gui_settings_socks_label": "SOCKS-port",
+ "gui_settings_authenticate_label": "Tor-autentiseringsinställningar",
+ "gui_settings_authenticate_no_auth_option": "Ingen autentisering eller kak-autentisering",
+ "gui_settings_authenticate_password_option": "Lösenord",
+ "gui_settings_password_label": "Lösenord",
+ "gui_settings_tor_bridges": "Stöd för Tor broar",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "Använd inte broar",
+ "gui_settings_tor_bridges_obfs4_radio_option": "Använd inbyggda obfs4 pluggbara transporter",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Använd inbyggda obfs4 pluggbara transporter (kräver obfs4proxy)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Använd inbyggda meek_lite (Azure) pluggbara transporter",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Använd inbyggda meek_lite (Azure) pluggbara transporter (kräver obfs4proxy)",
+ "gui_settings_meek_lite_expensive_warning": "Varning: meek_lite-broarna är mycket kostsamma för att Tor-projektet ska kunna köras.<br><br> Använd dem endast om det inte går att ansluta till Tor direkt, via obfs4-transporter eller andra normala broar.",
+ "gui_settings_tor_bridges_custom_radio_option": "Använd anpassade broar",
+ "gui_settings_tor_bridges_custom_label": "Du kan få broar från <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "Ingen av broarna du lagt till arbete.\nDubbelkolla dem eller lägga till andra.",
+ "gui_settings_button_save": "Spara",
+ "gui_settings_button_cancel": "Avbryt",
+ "gui_settings_button_help": "Hjälp",
+ "gui_settings_shutdown_timeout_checkbox": "Använd automatiska stopp-timern",
+ "gui_settings_shutdown_timeout": "Stoppa delningen vid:",
+ "settings_error_unknown": "Kan inte ansluta till Tor-regulatorn eftersom dina inställningar inte är vettiga.",
+ "settings_error_automatic": "Kunde inte ansluta till Tor-regulatorn. Körs Tor Browser (tillgänglig från torproject.org) i bakgrunden?",
+ "settings_error_socket_port": "Det går inte att ansluta till Tor-regulatorn på {}:{}.",
+ "settings_error_socket_file": "Det går inte att ansluta till Tor-regulatorn med socket-filen {}.",
+ "settings_error_auth": "Ansluten till {}:{}, men kan inte autentisera. Kanske är det här inte en Tor-regulator?",
+ "settings_error_missing_password": "Ansluten till Tor-regulatorn, men den kräver ett lösenord för att autentisera.",
+ "settings_error_unreadable_cookie_file": "Ansluten till Tor-regulatorn, men lösenordet kan vara fel, eller din användare är inte tillåtet att läsa kakfilen.",
+ "settings_error_bundled_tor_not_supported": "Användning av Tor-versionen som följer med OnionShare fungerar inte i utvecklarläge på Windows eller macOS.",
+ "settings_error_bundled_tor_timeout": "Det tar för lång tid att ansluta till Tor. Kanske är du inte ansluten till Internet, eller har en felaktig systemklocka?",
+ "settings_error_bundled_tor_broken": "OnionShare kunde inte ansluta till Tor i bakgrunden:\n{}",
+ "settings_test_success": "Ansluten till Tor-regulatorn.\n\nTor version: {}\nStöder efemära onion-tjänster: {}.\nStöder klientautentisering: {}.\nStöder nästa generations .onion-adresser: {}.",
+ "error_tor_protocol_error": "Det fanns ett fel med Tor: {}",
+ "error_tor_protocol_error_unknown": "Det fanns ett okänt fel med Tor",
+ "error_invalid_private_key": "Denna privata nyckeltyp stöds inte",
+ "connecting_to_tor": "Ansluter till Tor-nätverket",
+ "update_available": "Ny OnionShare utgiven. <a href='{}'>Klicka här</a> för att få den.<br><br>Du använder {} och den senaste är {}.",
+ "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…",
+ "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: Kanske är du inte ansluten till Tor, eller är OnionShare-webbplatsen nere?",
+ "update_not_available": "Du kör den senaste OnionShare.",
+ "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?",
+ "gui_tor_connection_ask_open_settings": "Ja",
+ "gui_tor_connection_ask_quit": "Avsluta",
+ "gui_tor_connection_error_settings": "Försök ändra hur OnionShare ansluter till Tor-nätverket i inställningarna.",
+ "gui_tor_connection_canceled": "Kunde inte ansluta till Tor.\n\nSe till att du är ansluten till Internet, öppna sedan OnionShare och ställ in anslutningen till Tor.",
+ "gui_tor_connection_lost": "Frånkopplad från Tor.",
+ "gui_server_started_after_timeout": "Automatiska stopp-timern tog slut innan servern startade.\nVänligen gör en ny delning.",
+ "gui_server_timeout_expired": "Automatiska stopp-timern har redan slutat.\nUppdatera den för att börja dela.",
+ "share_via_onionshare": "OnionShare den",
+ "gui_use_legacy_v2_onions_checkbox": "Använd äldre adresser",
+ "gui_save_private_key_checkbox": "Använd en beständig adress",
+ "gui_share_url_description": "<b>Alla</b> med denna OnionShare-adress kan <b>hämta</b> dina filer med hjälp av <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Alla</b> med denna OnionShare-adress kan <b>skicka</b> filer till din dator med hjälp av <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Denna delning kommer inte automatiskt att sluta.<br>< br>Varje efterföljande delning återanvänder adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)",
+ "gui_url_label_stay_open": "Denna delning kommer inte automatiskt att sluta.",
+ "gui_url_label_onetime": "Denna delning kommer att sluta efter första slutförandet.",
+ "gui_url_label_onetime_and_persistent": "Denna delning kommer inte automatiskt att sluta.<br>< br>Varje efterföljande delning kommer att återanvända adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)",
+ "gui_status_indicator_share_stopped": "Redo att dela",
+ "gui_status_indicator_share_working": "Börjar…",
+ "gui_status_indicator_share_started": "Delar",
+ "gui_status_indicator_receive_stopped": "Redo att ta emot",
+ "gui_status_indicator_receive_working": "Börjar…",
+ "gui_status_indicator_receive_started": "Tar emot",
+ "gui_file_info": "{} filer, {}",
+ "gui_file_info_single": "{} fil, {}",
+ "history_in_progress_tooltip": "{} pågår",
+ "history_completed_tooltip": "{} slutförda",
+ "info_in_progress_uploads_tooltip": "{} pågående sändning(ar)",
+ "info_completed_uploads_tooltip": "{} sändning(ar) slutförd(a)",
+ "error_cannot_create_downloads_dir": "Det gick inte att skapa mappen mottagningsläge: {}",
+ "receive_mode_downloads_dir": "Filer som skickas till dig visas i den här mappen: {}",
+ "receive_mode_warning": "Varning: Mottagningsläge låter personer skicka filer till din dator. Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.",
+ "gui_receive_mode_warning": "Mottagningsläge låter personer skicka filer till din dator.<br><br><b>Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.</h>",
+ "receive_mode_upload_starting": "Sändning av total storlek {} börjar",
+ "receive_mode_received_file": "Mottaget: {}",
+ "gui_mode_share_button": "Dela filer",
+ "gui_mode_receive_button": "Ta emot filer",
+ "gui_settings_receiving_label": "Mottagning-inställningar",
+ "gui_settings_downloads_label": "Spara filer till",
+ "gui_settings_downloads_button": "Bläddra",
+ "gui_settings_public_mode_checkbox": "Offentligt läge",
+ "systray_close_server_title": "OnionShare-servern stängd",
+ "systray_close_server_message": "En användare stängde servern",
+ "systray_page_loaded_title": "Sidan lästes in",
+ "systray_download_page_loaded_message": "En användare läste in hämtningssidan",
+ "systray_upload_page_loaded_message": "En användare läste in sändningssidan",
+ "gui_uploads": "Sändningshistoriken",
+ "gui_no_uploads": "Inga sändningar ännu",
+ "gui_clear_history": "Rensa alla",
+ "gui_upload_in_progress": "Sändning påbörjad {}",
+ "gui_upload_finished_range": "Skickade {} till {}",
+ "gui_upload_finished": "Skickade {}",
+ "gui_download_in_progress": "Hämtning påbörjad {}",
+ "gui_open_folder_error_nautilus": "Det går inte att öppna mappen eftersom nautilus inte är tillgänglig. Filen är här: {}",
+ "gui_settings_language_label": "Föredraget språk",
+ "gui_settings_language_changed_notice": "Starta om OnionShare för att din språkändring ska träda i kraft.",
+ "gui_add_files": "Lägg till filer",
+ "gui_add_folder": "Lägg till mapp",
+ "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar",
+ "error_cannot_create_data_dir": "Det gick inte att skapa OnionShare-datamapp: {}",
+ "receive_mode_data_dir": "Filer som skickas till dig visas i den här mappen: {}",
+ "gui_settings_data_dir_label": "Spara filer till",
+ "gui_settings_data_dir_browse_button": "Bläddra",
+ "systray_page_loaded_message": "OnionShare-adress lästes in",
+ "systray_share_started_title": "Delning börjades",
+ "systray_share_started_message": "Börjar skicka filer till någon",
+ "systray_share_completed_title": "Delning klar",
+ "systray_share_completed_message": "Filerna skickades",
+ "systray_share_canceled_title": "Delning avbruten",
+ "systray_share_canceled_message": "Någon har avbrutit att ta emot dina filer",
+ "systray_receive_started_title": "Mottagning startad",
+ "systray_receive_started_message": "Någon skickar filer till dig",
+ "gui_all_modes_history": "Historik",
+ "gui_all_modes_clear_history": "Rensa alla",
+ "gui_all_modes_transfer_started": "Började {}",
+ "gui_all_modes_transfer_finished_range": "Överförd {} - {}",
+ "gui_all_modes_transfer_finished": "Överförd {}",
+ "gui_all_modes_progress_complete": "%p%, {0} förflutit.",
+ "gui_all_modes_progress_starting": "{0} %s% (beräkning)",
+ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
+ "gui_share_mode_no_files": "Inga filer har skickats än",
+ "gui_share_mode_timeout_waiting": "Väntar på att avsluta sändningen",
+ "gui_receive_mode_no_files": "Inga filer har mottagits ännu",
+ "gui_receive_mode_timeout_waiting": "Väntar på att avsluta mottagande",
+ "gui_all_modes_transfer_canceled_range": "Avbröt {} - {}",
+ "gui_all_modes_transfer_canceled": "Avbröt {}"
+}
diff --git a/share/locale/tr.json b/share/locale/tr.json
index bd74f5f2..a5f5b429 100644
--- a/share/locale/tr.json
+++ b/share/locale/tr.json
@@ -1,43 +1,30 @@
{
- "connecting_ctrlport": "{0:d} portundaki gizli hizmet(GH) kurulumu için Tor kontrol portuna bağlanıyor.",
- "cant_connect_ctrlport": "Tor kontrol {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/",
- "cant_connect_socksport": "Tor SOCKS5 sunucu {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/",
- "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.",
- "wait_for_hs": "GH hazır olması bekleniyor:",
- "wait_for_hs_trying": "Deneniyor...",
- "wait_for_hs_nope": "Henüz hazır değil.",
- "wait_for_hs_yup": "Hazır!",
- "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:",
- "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın",
+ "preparing_files": "Sıkıştırma dosyaları.",
+ "give_this_url": "Bu adresi alıcıya verin:",
+ "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın",
"not_a_file": "{0:s} dosya değil.",
- "download_page_loaded": "İndirme sayfası yüklendi",
"other_page_loaded": "Diğer sayfa yüklendi",
"closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor",
"large_filesize": "Uyarı: Büyük dosyaların gönderimi saatler sürebilir",
- "error_tails_invalid_port": "Geçersiz değer, port sayı olmalıdır",
- "error_tails_unknown_root": "Tails ana işlemi ile ilgili bilinmeyen hata",
- "help_tails_port": "Sadece Tails: port for opening firewall, starting onion service",
"help_local_only": "Tor kullanmaya kalkışmayın: sadece geliştirme için",
"help_stay_open": "İndirme tamamlandıktan sonra gizli hizmeti çalıştırmaya devam et",
- "help_transparent_torification": "Sistemim apaçık torlu",
"help_debug": "Hata kayıtlarını diske kaydet",
"help_filename": "Paylaşmak için dosya ve klasörler listesi",
"gui_drag_and_drop": "Dosyaları buraya\n Sürükle ve Bırak",
"gui_add": "Ekle",
"gui_delete": "Sil",
"gui_choose_items": "Seç",
- "gui_start_server": "Paylaşımı Başlat",
- "gui_stop_server": "Paylaşımı Durdur",
+ "gui_share_start_server": "Paylaşımı Başlat",
+ "gui_share_stop_server": "Paylaşımı Durdur",
"gui_copy_url": "URL Kopyala",
"gui_downloads": "İndirilenler:",
"gui_canceled": "İptal edilen",
"gui_copied_url": "Panoya kopyalanan URL",
- "gui_starting_server1": "Tor gizli hizmeti başlatılıyor...",
- "gui_starting_server2": "Dosyalar hazırlanıyor...",
- "gui_starting_server3": "Tor gizli hizmeti bekleniyor...",
"gui_please_wait": "Lütfen bekleyin...",
- "error_hs_dir_cannot_create": "Gizli hizmet klasörü {0:s} oluşturulamıyor",
- "error_hs_dir_not_writable": "Gizle hizmet klasörü {0:s} yazılabilir değil",
- "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor",
- "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%"
+ "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%",
+ "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla.",
+ "give_this_url_receive": "Bu adresi gönderene ver:",
+ "not_a_readable_file": "{0:s} okunabilir bir dosya değil.",
+ "no_available_port": "Onion servisini başlatmak için uygun bir port bulunamadı",
+ "close_on_timeout": "Otomatik durma zamanlayıcısının bitmesi nedeniyle durdu"
}
diff --git a/share/locale/wo.json b/share/locale/wo.json
new file mode 100644
index 00000000..a67a5f75
--- /dev/null
+++ b/share/locale/wo.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/yo.json b/share/locale/yo.json
new file mode 100644
index 00000000..25cd5c48
--- /dev/null
+++ b/share/locale/yo.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "timeout_upload_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "",
+ "gui_delete": "",
+ "gui_choose_items": "",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "",
+ "gui_settings_password_label": "",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json
new file mode 100644
index 00000000..6a3bf7b7
--- /dev/null
+++ b/share/locale/zh_Hans.json
@@ -0,0 +1,216 @@
+{
+ "config_onion_service": "在端口{0:d}上设置洋葱服务。",
+ "preparing_files": "正在压缩文件.",
+ "give_this_url": "把这个地址给收件人:",
+ "give_this_url_stealth": "向收件人提供此地址和HidServAuth行:",
+ "give_this_url_receive": "把这个地址交给发件人:",
+ "give_this_url_receive_stealth": "把这个地址和HidServAuth交给发送者:",
+ "ctrlc_to_stop": "按Ctrl+C停止服务器",
+ "not_a_file": "{0:s}不是有效文件。",
+ "not_a_readable_file": "{0:s}不是可读文件.",
+ "no_available_port": "找不到可用于开启onion服务的端口",
+ "other_page_loaded": "地址已加载完成",
+ "close_on_timeout": "终止 原因:自动停止计时器的时间已到",
+ "closing_automatically": "终止 原因:传输已完成",
+ "timeout_download_still_running": "",
+ "large_filesize": "警告:分享大文件可能会用上数小时",
+ "systray_menu_exit": "退出",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "不使用Tor(只限开发测试)",
+ "help_stay_open": "文件传输完成后继续分享",
+ "help_shutdown_timeout": "超过给定时间(秒)后,终止分享.",
+ "help_stealth": "使用服务端认证(高级选项)",
+ "help_receive": "仅接收分享的文件,不发送",
+ "help_debug": "将OnionShare错误日志记录到stdout,将web错误日志记录到磁盘",
+ "help_filename": "要分享的文件或文件夹的列表",
+ "help_config": "自定义JSON配置文件的路径(可选)",
+ "gui_drag_and_drop": "将文件或文件夹拖动到这里来开始分享",
+ "gui_add": "添加",
+ "gui_delete": "删除",
+ "gui_choose_items": "选取",
+ "gui_share_start_server": "开始分享",
+ "gui_share_stop_server": "停止分享",
+ "gui_share_stop_server_shutdown_timeout": "停止分享(还剩{}秒)",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "在{}自动停止",
+ "gui_receive_start_server": "开启接受模式",
+ "gui_receive_stop_server": "停止接受模式",
+ "gui_receive_stop_server_shutdown_timeout": "停止接受模式(还剩{}秒)",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "在{}自动停止",
+ "gui_copy_url": "复制地址",
+ "gui_copy_hidservauth": "复制HidServAuth",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "已取消",
+ "gui_copied_url_title": "已复制的OnionShare地址",
+ "gui_copied_url": "OnionShare地址已复制到剪贴板",
+ "gui_copied_hidservauth_title": "已复制的HidServAuth",
+ "gui_copied_hidservauth": "HidServAuth行已复制到剪贴板",
+ "gui_please_wait": "起始中...点击这里可取消.",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "版本: OnionShare {0:s} | https://onionshare.org/",
+ "gui_quit_title": "再等等",
+ "gui_share_quit_warning": "您有文件正在传输中...您确定要退出OnionShare吗?",
+ "gui_receive_quit_warning": "您有文件还正在接收中...您确定要退出OnionShare吗?",
+ "gui_quit_warning_quit": "退出",
+ "gui_quit_warning_dont_quit": "取消",
+ "error_rate_limit": "有人您对地址发出过多错误请求,这很可能说明有人在尝试猜测您的地址.因此为了安全OinionShare已终止服务.请重新开启分享并且向收件人发送新地址.",
+ "zip_progress_bar_format": "压缩中: %p%",
+ "error_stealth_not_supported": "要使用服务端认证,您至少需要的最低版本要求是:Tor 0.2.9.1-alpha (or Tor Browser 6.5)和python3-stem 1.5.0.两者缺一不可,同时需要.",
+ "error_ephemeral_not_supported": "OnionShare至少同时需要Tor 0.2.7.1和python3-stem 1.4.0来运行.",
+ "gui_settings_window_title": "设置",
+ "gui_settings_whats_this": "<a href='{0:s}'>这是什么?</a>",
+ "gui_settings_stealth_option": "使用客户端认证",
+ "gui_settings_stealth_hidservauth_string": "已保存了你的私钥用于重复使用,意味着您现在可以\n点击这里来复制您的HidServAuth.",
+ "gui_settings_autoupdate_label": "检查新版本",
+ "gui_settings_autoupdate_option": "有新版本可用时告知我",
+ "gui_settings_autoupdate_timestamp": "上次检查更新的时间:{}",
+ "gui_settings_autoupdate_timestamp_never": "从不",
+ "gui_settings_autoupdate_check_button": "检查新版本",
+ "gui_settings_general_label": "常规设置",
+ "gui_settings_sharing_label": "分享设置",
+ "gui_settings_close_after_first_download_option": "文件发送完成后停止分享",
+ "gui_settings_connection_type_label": "OnionShare应如何连接Tor?",
+ "gui_settings_connection_type_bundled_option": "使用OnionShare内置的tor",
+ "gui_settings_connection_type_automatic_option": "尝试使用Tor Browser(Tor浏览器)的设置",
+ "gui_settings_connection_type_control_port_option": "用特定端口连接",
+ "gui_settings_connection_type_socket_file_option": "使用socket文档的设置连接",
+ "gui_settings_connection_type_test_button": "测试tor连接",
+ "gui_settings_control_port_label": "控制端口",
+ "gui_settings_socket_file_label": "Socket配置文档",
+ "gui_settings_socks_label": "SOCKS 端口",
+ "gui_settings_authenticate_label": "Tor认证设置",
+ "gui_settings_authenticate_no_auth_option": "无须认证,或者使用的是cookie认证",
+ "gui_settings_authenticate_password_option": "密码",
+ "gui_settings_password_label": "密码",
+ "gui_settings_tor_bridges": "Tor网桥设置",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "不使用网桥",
+ "gui_settings_tor_bridges_obfs4_radio_option": "使用内置的obfs4 pluggable transports",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "使用内置的obfs4 pluggable transports(需要obfs4代理)",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "使用内置meek_lite (Azure) pluggable transports",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "使用内置meek_lite (Azure) pluggable transports (需要obfs4代理)",
+ "gui_settings_meek_lite_expensive_warning": "警告:meek_lite类型的网桥对Tor流量产生的负担很大,<br>请只在无法直接使用tor,且obfs4 transport和其他网桥都无法连接时才使用.<br>.",
+ "gui_settings_tor_bridges_custom_radio_option": "使用自定义网桥",
+ "gui_settings_tor_bridges_custom_label": "您可以从这里得到网桥地址<a href=\"https://bridges.torproject.org/options\">\nhttps://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_invalid": "您所添加的网桥无法工作.\n请双击它们或者添加其它网桥.",
+ "gui_settings_button_save": "保存",
+ "gui_settings_button_cancel": "取消",
+ "gui_settings_button_help": "帮助",
+ "gui_settings_shutdown_timeout_checkbox": "使用自动停止计时器",
+ "gui_settings_shutdown_timeout": "在(时间)停止分享",
+ "settings_error_unknown": "无法连接Tor控制件,因为您的设置无法被理解.",
+ "settings_error_automatic": "无法连接tor控制件.Tor浏览器是否在后台工作?(从torproject.org可以获得Tor Browser)",
+ "settings_error_socket_port": "在socket端口{}:{}无法连接tor控制件.",
+ "settings_error_socket_file": "无法使用socket配置文档的设置连接tor控制件",
+ "settings_error_auth": "已连接到了{}:{},但是无法认证,也许这不是tor控制件?",
+ "settings_error_missing_password": "已连接到tor控制件,但需要密码来认证.",
+ "settings_error_unreadable_cookie_file": "已连接到tor控制件,但可能密码错误,或者没有读取cookie文件的权限.",
+ "settings_error_bundled_tor_not_supported": "OnionShare自带的Tor无法在Windows或macOS下运行开发者模式",
+ "settings_error_bundled_tor_timeout": "尝试连接tor的用时过长,也许您的网络有问题,或者是系统时间不准确?",
+ "settings_error_bundled_tor_broken": "OnionShare无法在后台连接Tor\n{}",
+ "settings_test_success": "已连接到Tor控制件\n\nTor版本: {}\n支持短期onion服务: {}.\n支持客户端认证: {}.\n支持新一代.onion地址: {}.",
+ "error_tor_protocol_error": "Tor出现错误: {}",
+ "error_tor_protocol_error_unknown": "Tor出现未知错误",
+ "error_invalid_private_key": "不支持这种类型的私钥",
+ "connecting_to_tor": "正在连接Tor网络",
+ "update_available": "有新版本的OnionShare可用:<a href='{}'>请点击这里</a> 来获得.<br><br>您在使用的版本为 {} 最新的可用版本为 {}.",
+ "update_error_check_error": "无法检查更新:OnionShare官网对最新版本无法识别'{}'…",
+ "update_error_invalid_latest_version": "无法检查更新:也许您没有连接到Tor?或者OnionShare官网不可用?",
+ "update_not_available": "您现在运行的OnionShare为最新版本.",
+ "gui_tor_connection_ask": "打开设置来查看Tor连接?",
+ "gui_tor_connection_ask_open_settings": "是的",
+ "gui_tor_connection_ask_quit": "退出",
+ "gui_tor_connection_error_settings": "请尝试在设置中设定OnionShare连接Tor的方式.",
+ "gui_tor_connection_canceled": "无法连接Tor.\n\n请确保您一连接到网络,然后重启OnionShare并设置Tor连接.",
+ "gui_tor_connection_lost": "已和Tor断开连接.",
+ "gui_server_started_after_timeout": "在服务开始之前自动停止计时器的时间已到.\n请建立新的分享.",
+ "gui_server_timeout_expired": "自动停止计时器的时间已到.\n请更新其设置来开始分享.",
+ "share_via_onionshare": "用OnionShare来分享",
+ "gui_use_legacy_v2_onions_checkbox": "使用古老的地址",
+ "gui_save_private_key_checkbox": "使用长期地址",
+ "gui_share_url_description": "<b>任何人</b>只要拥有这个OnionShare 地址,都可以用<b>Tor浏览器</b>来从您的设备进行文件<b>下载</b>:<img src='{}' />",
+ "gui_receive_url_description": "<b>任何人</b>只要拥有这个OnionShare 地址,都可以用<b>Tor浏览器</b>来给你的设备进行文件<b>上传</b>:<img src='{}' />",
+ "gui_url_label_persistent": "这个分享不会自动停止.<br><br>每个子列分享都会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)",
+ "gui_url_label_stay_open": "这个分享不会自动停止.",
+ "gui_url_label_onetime": "这个分享将在初次完成后终止.",
+ "gui_url_label_onetime_and_persistent": "这个分享不会自动停止.<br><br>每个子列分享都将会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)",
+ "gui_status_indicator_share_stopped": "准备分享",
+ "gui_status_indicator_share_working": "正在初始话…",
+ "gui_status_indicator_share_started": "分享中",
+ "gui_status_indicator_receive_stopped": "准备接收",
+ "gui_status_indicator_receive_working": "正在初始化…",
+ "gui_status_indicator_receive_started": "正在接收",
+ "gui_file_info": "{} 个文件, {}",
+ "gui_file_info_single": "{} 个文件, {}",
+ "history_in_progress_tooltip": "{}在进行中",
+ "history_completed_tooltip": "{}已完成",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "警告:接收模式下允许他人对您的设备上传文件.有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.",
+ "gui_receive_mode_warning": "接收模式下允许他人对您的设备上传文件.<br><br><b>有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.</b>",
+ "receive_mode_upload_starting": "上传文件的大小为{}正在开始",
+ "receive_mode_received_file": "接收到: {}",
+ "gui_mode_share_button": "分享文件",
+ "gui_mode_receive_button": "接收文件",
+ "gui_settings_receiving_label": "接收设置",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "分享轨迹",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "公共模式",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "页面已加载",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "清除所有",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "无法打开文件夹,原因:nautilus不可用.文件在这里: {}",
+ "gui_settings_language_label": "首选语言",
+ "gui_settings_language_changed_notice": "请重启OnionShare以使您的语言改变设定生效.",
+ "gui_add_files": "添加文件",
+ "gui_add_folder": "添加文件夹",
+ "gui_connect_to_tor_for_onion_settings": "连接Tor来查看onion服务的设置",
+ "error_cannot_create_data_dir": "无法建立OnionShare文件夹: {}",
+ "receive_mode_data_dir": "您收到的文件会出现在这个文件夹: {}",
+ "gui_settings_data_dir_label": "将文件保存到",
+ "gui_settings_data_dir_browse_button": "浏览",
+ "systray_page_loaded_message": "OnionShare地址已加载",
+ "systray_share_started_title": "分享开始",
+ "systray_share_started_message": "开始向某人发送文件",
+ "systray_share_completed_title": "分享完成",
+ "systray_share_completed_message": "文件发送完成",
+ "systray_share_canceled_title": "分享已取消",
+ "systray_share_canceled_message": "某人取消了接收您的文件",
+ "systray_receive_started_title": "接收开始",
+ "systray_receive_started_message": "某人在向您发送文件",
+ "gui_all_modes_history": "历史",
+ "gui_all_modes_clear_history": "清除全部",
+ "gui_all_modes_transfer_started": "已开始{}",
+ "gui_all_modes_transfer_finished_range": "已传输 {} - {}",
+ "gui_all_modes_transfer_finished": "已传输完成 {}",
+ "gui_all_modes_progress_complete": "%p%, {0:s} 已完成.",
+ "gui_all_modes_progress_starting": "{0:s}, %p% (计算中)",
+ "gui_all_modes_progress_eta": "{0:s}, 预计完成时间: {1:s}, %p%",
+ "gui_share_mode_no_files": "还没有文件发出",
+ "gui_share_mode_timeout_waiting": "等待结束发送",
+ "gui_receive_mode_no_files": "还没有接收文件",
+ "gui_receive_mode_timeout_waiting": "等待接收完成",
+ "gui_settings_onion_label": "Onion设置",
+ "gui_all_modes_transfer_canceled_range": "已取消 {} - {}",
+ "gui_all_modes_transfer_canceled": "已取消 {}"
+}
diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json
new file mode 100644
index 00000000..c24d13ce
--- /dev/null
+++ b/share/locale/zh_Hant.json
@@ -0,0 +1,185 @@
+{
+ "config_onion_service": "",
+ "preparing_files": "",
+ "give_this_url": "",
+ "give_this_url_stealth": "",
+ "give_this_url_receive": "",
+ "give_this_url_receive_stealth": "",
+ "ctrlc_to_stop": "",
+ "not_a_file": "",
+ "not_a_readable_file": "",
+ "no_available_port": "",
+ "other_page_loaded": "",
+ "close_on_timeout": "",
+ "closing_automatically": "",
+ "timeout_download_still_running": "",
+ "large_filesize": "",
+ "systray_menu_exit": "離開",
+ "systray_download_started_title": "",
+ "systray_download_started_message": "",
+ "systray_download_completed_title": "",
+ "systray_download_completed_message": "",
+ "systray_download_canceled_title": "",
+ "systray_download_canceled_message": "",
+ "systray_upload_started_title": "",
+ "systray_upload_started_message": "",
+ "help_local_only": "",
+ "help_stay_open": "",
+ "help_shutdown_timeout": "",
+ "help_stealth": "",
+ "help_receive": "",
+ "help_debug": "",
+ "help_filename": "",
+ "help_config": "",
+ "gui_drag_and_drop": "",
+ "gui_add": "新增",
+ "gui_delete": "刪除",
+ "gui_choose_items": "選擇",
+ "gui_share_start_server": "",
+ "gui_share_stop_server": "",
+ "gui_share_stop_server_shutdown_timeout": "",
+ "gui_share_stop_server_shutdown_timeout_tooltip": "",
+ "gui_receive_start_server": "",
+ "gui_receive_stop_server": "",
+ "gui_receive_stop_server_shutdown_timeout": "",
+ "gui_receive_stop_server_shutdown_timeout_tooltip": "",
+ "gui_copy_url": "",
+ "gui_copy_hidservauth": "",
+ "gui_downloads": "",
+ "gui_no_downloads": "",
+ "gui_canceled": "取消",
+ "gui_copied_url_title": "",
+ "gui_copied_url": "",
+ "gui_copied_hidservauth_title": "",
+ "gui_copied_hidservauth": "",
+ "gui_please_wait": "",
+ "gui_download_upload_progress_complete": "",
+ "gui_download_upload_progress_starting": "",
+ "gui_download_upload_progress_eta": "",
+ "version_string": "",
+ "gui_quit_title": "",
+ "gui_share_quit_warning": "",
+ "gui_receive_quit_warning": "",
+ "gui_quit_warning_quit": "離開",
+ "gui_quit_warning_dont_quit": "",
+ "error_rate_limit": "",
+ "zip_progress_bar_format": "",
+ "error_stealth_not_supported": "",
+ "error_ephemeral_not_supported": "",
+ "gui_settings_window_title": "設定",
+ "gui_settings_whats_this": "",
+ "gui_settings_stealth_option": "",
+ "gui_settings_stealth_hidservauth_string": "",
+ "gui_settings_autoupdate_label": "檢查新版本",
+ "gui_settings_autoupdate_option": "",
+ "gui_settings_autoupdate_timestamp": "",
+ "gui_settings_autoupdate_timestamp_never": "不使用",
+ "gui_settings_autoupdate_check_button": "",
+ "gui_settings_general_label": "一般設定",
+ "gui_settings_sharing_label": "",
+ "gui_settings_close_after_first_download_option": "",
+ "gui_settings_connection_type_label": "",
+ "gui_settings_connection_type_bundled_option": "",
+ "gui_settings_connection_type_automatic_option": "",
+ "gui_settings_connection_type_control_port_option": "",
+ "gui_settings_connection_type_socket_file_option": "",
+ "gui_settings_connection_type_test_button": "",
+ "gui_settings_control_port_label": "管理連接埠",
+ "gui_settings_socket_file_label": "",
+ "gui_settings_socks_label": "",
+ "gui_settings_authenticate_label": "",
+ "gui_settings_authenticate_no_auth_option": "",
+ "gui_settings_authenticate_password_option": "Password",
+ "gui_settings_password_label": "Password",
+ "gui_settings_tor_bridges": "",
+ "gui_settings_tor_bridges_no_bridges_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option": "",
+ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "",
+ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "",
+ "gui_settings_meek_lite_expensive_warning": "",
+ "gui_settings_tor_bridges_custom_radio_option": "",
+ "gui_settings_tor_bridges_custom_label": "",
+ "gui_settings_tor_bridges_invalid": "",
+ "gui_settings_button_save": "保存",
+ "gui_settings_button_cancel": "",
+ "gui_settings_button_help": "協助",
+ "gui_settings_shutdown_timeout_checkbox": "",
+ "gui_settings_shutdown_timeout": "",
+ "settings_error_unknown": "",
+ "settings_error_automatic": "",
+ "settings_error_socket_port": "",
+ "settings_error_socket_file": "",
+ "settings_error_auth": "",
+ "settings_error_missing_password": "",
+ "settings_error_unreadable_cookie_file": "",
+ "settings_error_bundled_tor_not_supported": "",
+ "settings_error_bundled_tor_timeout": "",
+ "settings_error_bundled_tor_broken": "",
+ "settings_test_success": "",
+ "error_tor_protocol_error": "",
+ "error_tor_protocol_error_unknown": "",
+ "error_invalid_private_key": "",
+ "connecting_to_tor": "",
+ "update_available": "",
+ "update_error_check_error": "",
+ "update_error_invalid_latest_version": "",
+ "update_not_available": "",
+ "gui_tor_connection_ask": "",
+ "gui_tor_connection_ask_open_settings": "",
+ "gui_tor_connection_ask_quit": "離開",
+ "gui_tor_connection_error_settings": "",
+ "gui_tor_connection_canceled": "",
+ "gui_tor_connection_lost": "",
+ "gui_server_started_after_timeout": "",
+ "gui_server_timeout_expired": "",
+ "share_via_onionshare": "",
+ "gui_use_legacy_v2_onions_checkbox": "",
+ "gui_save_private_key_checkbox": "",
+ "gui_share_url_description": "",
+ "gui_receive_url_description": "",
+ "gui_url_label_persistent": "",
+ "gui_url_label_stay_open": "",
+ "gui_url_label_onetime": "",
+ "gui_url_label_onetime_and_persistent": "",
+ "gui_status_indicator_share_stopped": "",
+ "gui_status_indicator_share_working": "",
+ "gui_status_indicator_share_started": "分享",
+ "gui_status_indicator_receive_stopped": "",
+ "gui_status_indicator_receive_working": "",
+ "gui_status_indicator_receive_started": "收取",
+ "gui_file_info": "",
+ "gui_file_info_single": "",
+ "history_in_progress_tooltip": "",
+ "history_completed_tooltip": "",
+ "info_in_progress_uploads_tooltip": "",
+ "info_completed_uploads_tooltip": "",
+ "error_cannot_create_downloads_dir": "",
+ "receive_mode_downloads_dir": "",
+ "receive_mode_warning": "",
+ "gui_receive_mode_warning": "",
+ "receive_mode_upload_starting": "",
+ "receive_mode_received_file": "",
+ "gui_mode_share_button": "",
+ "gui_mode_receive_button": "",
+ "gui_settings_receiving_label": "",
+ "gui_settings_downloads_label": "",
+ "gui_settings_downloads_button": "瀏覽",
+ "gui_settings_receive_allow_receiver_shutdown_checkbox": "",
+ "gui_settings_public_mode_checkbox": "",
+ "systray_close_server_title": "",
+ "systray_close_server_message": "",
+ "systray_page_loaded_title": "",
+ "systray_download_page_loaded_message": "",
+ "systray_upload_page_loaded_message": "",
+ "gui_uploads": "",
+ "gui_no_uploads": "",
+ "gui_clear_history": "",
+ "gui_upload_in_progress": "",
+ "gui_upload_finished_range": "",
+ "gui_upload_finished": "",
+ "gui_download_in_progress": "",
+ "gui_open_folder_error_nautilus": "",
+ "gui_settings_language_label": "",
+ "gui_settings_language_changed_notice": ""
+}
diff --git a/share/static/css/style.css b/share/static/css/style.css
new file mode 100644
index 00000000..0751d306
--- /dev/null
+++ b/share/static/css/style.css
@@ -0,0 +1,243 @@
+.clearfix:after {
+ content: ".";
+ display: block;
+ clear: both;
+ visibility: hidden;
+ line-height: 0;
+ height: 0;
+}
+
+body {
+ margin: 0;
+ font-family: Helvetica, sans-serif;
+}
+
+header {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ background: #fcfcfc;
+ background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%);
+ padding: 0.8rem;
+}
+
+header .logo {
+ vertical-align: middle;
+ width: 45px;
+ height: 45px;
+}
+
+header h1 {
+ display: inline-block;
+ margin: 0 0 0 0.5rem;
+ vertical-align: middle;
+ font-weight: normal;
+ font-size: 1.5rem;
+ color: #666666;
+}
+
+header .right {
+ float: right;
+ font-size: .75rem;
+}
+
+header .right ul li {
+ display: inline;
+ margin: 0 0 0 .5rem;
+ font-size: 1rem;
+}
+
+.button {
+ color: #ffffff;
+ background-color: #4e064f;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ text-decoration: none;
+ margin-left: 1rem;
+ cursor: pointer;
+}
+
+.close-button {
+ color: #ffffff;
+ background-color: #c90c0c;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ text-decoration: none;
+ margin-left: 1rem;
+ cursor: pointer;
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+}
+
+table.file-list {
+ width: 100%;
+ margin: 0 auto;
+ border-collapse: collapse;
+}
+
+table.file-list th {
+ text-align: left;
+ text-transform: uppercase;
+ font-weight: normal;
+ color: #666666;
+ padding: 0.5rem;
+}
+
+table.file-list tr {
+ border-bottom: 1px solid #e0e0e0;
+}
+
+table.file-list td {
+ white-space: nowrap;
+ padding: 0.5rem 10rem 0.5rem 0.8rem;
+}
+
+table.file-list td img {
+ vertical-align: middle;
+ margin-right: 0.5rem;
+}
+
+table.file-list td:last-child {
+ width: 100%;
+}
+
+.upload-wrapper {
+ align-items: center;
+ justify-content: center;
+ min-height: 400px;
+ text-align: center;
+}
+
+.upload-wrapper img.logo {
+ width: 120px;
+ height: 120px;
+}
+
+.upload-wrapper .upload-header {
+ font-size: 30px;
+ font-weight: normal;
+ color: #666666;
+ margin: 0 0 10px 0;
+}
+
+.upload-wrapper .upload-description {
+ color: #666666;
+ margin: 0 0 20px 0;
+}
+
+div#uploads {
+ width: 800px;
+ max-width: 90%;
+ margin: 0 auto;
+}
+
+div#uploads .upload {
+ border: 1px solid #DDDDDD;
+ margin: 20px 0;
+ padding: 10px;
+ text-align: left;
+}
+
+div#uploads .upload .upload-filename {
+ font-weight: bold;
+ font-family: monospace;
+ font-size: 1.1em;
+ margin-bottom: 5px;
+}
+
+div#uploads .upload .upload-status {
+ color: #999999;
+ font-size: 0.9em;
+ margin-bottom: 5px;
+}
+
+div#uploads .upload input.cancel {
+ color: #d0011b;
+ border: 0;
+ background: none;
+ box-shadow: none;
+ border-radius: 0px;
+ cursor: pointer;
+ font-family: sans-serif;
+ font-size: 12px;
+ text-decoration: none;
+ display: inline-block;
+ float:right;
+}
+
+div#uploads .upload progress {
+ width: 100%;
+ height: 20px;
+}
+
+ul.flashes {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ width: 800px;
+ max-width: 90%;
+ margin: 0 auto;
+}
+
+ul.flashes li {
+ margin: 0 0 5px 0;
+ padding: 5px;
+ list-style: none;
+ text-align: left;
+}
+
+li.error {
+ color: #d0011b;
+}
+
+li.info {
+ color: #5fa416;
+}
+
+.closed-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 400px;
+}
+
+.info {
+ text-align: center;
+}
+
+.info img {
+ width: 120px;
+ height: 120px;
+}
+
+.info .info-header {
+ font-size: 30px;
+ font-weight: normal;
+ color: #666666;
+ margin: 0 0 10px 0;
+}
+
+.info .info-description {
+ color: #666666;
+ margin: 0 0 20px 0;
+}
+
+div#noscript {
+ border: 1px solid #e55454;
+ text-align: left;
+ color: #e55454;
+ padding: 1em;
+ line-height: 150%;
+ max-width: 900px;
+ margin: 100px 2em 0 2em;
+}
+
+div#noscript a, div#noscript a:visited {
+ color: #e27f7f;
+}
+
+.disable-noscript-xss-wrapper {
+ max-width: 900px;
+ margin: 0 auto;
+}
diff --git a/share/static/img/ajax.gif b/share/static/img/ajax.gif
new file mode 100644
index 00000000..01d955aa
--- /dev/null
+++ b/share/static/img/ajax.gif
Binary files differ
diff --git a/share/images/favicon.ico b/share/static/img/favicon.ico
index 63e65d8b..63e65d8b 100644
--- a/share/images/favicon.ico
+++ b/share/static/img/favicon.ico
Binary files differ
diff --git a/share/static/img/logo.png b/share/static/img/logo.png
new file mode 100644
index 00000000..43884c1f
--- /dev/null
+++ b/share/static/img/logo.png
Binary files differ
diff --git a/share/static/img/logo_large.png b/share/static/img/logo_large.png
new file mode 100644
index 00000000..ee8f26ac
--- /dev/null
+++ b/share/static/img/logo_large.png
Binary files differ
diff --git a/share/images/web_file.png b/share/static/img/web_file.png
index 1931aff0..1931aff0 100644
--- a/share/images/web_file.png
+++ b/share/static/img/web_file.png
Binary files differ
diff --git a/share/images/web_folder.png b/share/static/img/web_folder.png
index 3ca5df21..3ca5df21 100644
--- a/share/images/web_folder.png
+++ b/share/static/img/web_folder.png
Binary files differ
diff --git a/share/static/js/jquery-3.3.1.min.js b/share/static/js/jquery-3.3.1.min.js
new file mode 100644
index 00000000..4d9b3a25
--- /dev/null
+++ b/share/static/js/jquery-3.3.1.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:"jQuery"+("3.3.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==c.call(e))&&(!(t=i(e))||"function"==typeof(n=f.call(t,"constructor")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),"function"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function C(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",I="\\["+M+"*("+R+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+R+"))|)"+M+"*\\]",W=":("+R+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+I+")*)|.*)\\)|)",$=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),F=new RegExp("^"+M+"*,"+M+"*"),_=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="<a id='"+b+"'></a><select id='"+b+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:he(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:he(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=r.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=fe(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=pe(t);function ye(){}ye.prototype=r.filters=r.pseudos,r.setFilters=new ye,a=oe.tokenize=function(e,t){var n,i,o,a,s,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=r.preFilter;while(s){n&&!(i=F.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),n=!1,(i=_.exec(s))&&(n=i.shift(),o.push({value:n,type:i[0].replace(B," ")}),s=s.slice(n.length));for(a in r.filter)!(i=V[a].exec(s))||l[a]&&!(i=l[a](i))||(n=i.shift(),o.push({value:n,type:a,matches:i}),s=s.slice(n.length));if(!n)break}return t?s.length:s?oe.error(e):k(e,u).slice(0)};function ve(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function me(e,t,n){var r=t.dir,i=t.next,o=i||r,a=n&&"parentNode"===o,s=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||a)return e(t,n,i);return!1}:function(t,n,u){var l,c,f,p=[T,s];if(u){while(t=t[r])if((1===t.nodeType||a)&&e(t,n,u))return!0}else while(t=t[r])if(1===t.nodeType||a)if(f=t[b]||(t[b]={}),c=f[t.uniqueID]||(f[t.uniqueID]={}),i&&i===t.nodeName.toLowerCase())t=t[r]||t;else{if((l=c[o])&&l[0]===T&&l[1]===s)return p[2]=l[2];if(c[o]=p,p[2]=e(t,n,u))return!0}return!1}}function xe(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r<i;r++)oe(e,t[r],n);return n}function we(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Te(e,t,n,r,i,o){return r&&!r[b]&&(r=Te(r)),i&&!i[b]&&(i=Te(i,o)),se(function(o,a,s,u){var l,c,f,p=[],d=[],h=a.length,g=o||be(t||"*",s.nodeType?[s]:s,[]),y=!e||!o&&t?g:we(g,p,e,s,u),v=n?i||(o?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r){l=we(v,d),r(l,[],s,u),c=l.length;while(c--)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f))}if(o){if(i||e){if(i){l=[],c=v.length;while(c--)(f=v[c])&&l.push(y[c]=f);i(null,v=[],l,u)}c=v.length;while(c--)(f=v[c])&&(l=i?O(o,f):p[c])>-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u<o;u++)if(n=r.relative[e[u].type])p=[me(xe(p),n)];else{if((n=r.filter[e[u].type].apply(null,e[u].matches))[b]){for(i=++u;i<o;i++)if(r.relative[e[i].type])break;return Te(u>1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u<i&&Ce(e.slice(u,i)),i<o&&Ce(e=e.slice(i)),i<o&&ve(e))}p.push(n)}return xe(p)}function Ee(e,t){var n=t.length>0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t<r;t++)if(w.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)w.find(e,i[t],n);return r>1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(w.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&w(e);if(!D.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s<o.length)!1===o[s].apply(n[0],n[1])&&e.stopOnFalse&&(s=o.length,n=!1)}e.memory||(n=!1),t=!1,i&&(o=n?[]:"")},l={add:function(){return o&&(n&&!t&&(s=o.length-1,a.push(n)),function t(n){w.each(n,function(n,r){g(r)?e.unique&&l.has(r)||o.push(r):r&&r.length&&"string"!==x(r)&&t(r)})}(arguments),n&&!t&&u()),this},remove:function(){return w.each(arguments,function(e,t){var n;while((n=w.inArray(t,o,n))>-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t<o)){if((e=r.apply(s,u))===n.promise())throw new TypeError("Thenable self-resolution");l=e&&("object"==typeof e||"function"==typeof e)&&e.then,g(l)?i?l.call(e,a(o,n,I,i),a(o,n,W,i)):(o++,l.call(e,a(o,n,I,i),a(o,n,W,i),a(o,n,I,n.notifyWith))):(r!==I&&(s=void 0,u=[e]),(i||n.resolveWith)(s,u))}},c=i?l:function(){try{l()}catch(e){w.Deferred.exceptionHook&&w.Deferred.exceptionHook(e,c.stackTrace),t+1>=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},X=/^-ms-/,U=/-([a-z])/g;function V(e,t){return t.toUpperCase()}function G(e){return e.replace(X,"ms-").replace(U,V)}var Y=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Q(){this.expando=w.expando+Q.uid++}Q.uid=1,Q.prototype={cache:function(e){var t=e[this.expando];return t||(t={},Y(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[G(t)]=n;else for(r in t)i[G(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][G(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(G):(t=G(t))in r?[t]:t.match(M)||[]).length;while(n--)delete r[t[n]]}(void 0===t||w.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!w.isEmptyObject(t)}};var J=new Q,K=new Q,Z=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,ee=/[A-Z]/g;function te(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:Z.test(e)?JSON.parse(e):e)}function ne(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(ee,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n=te(n)}catch(e){}K.set(e,t,n)}else n=void 0;return n}w.extend({hasData:function(e){return K.hasData(e)||J.hasData(e)},data:function(e,t,n){return K.access(e,t,n)},removeData:function(e,t){K.remove(e,t)},_data:function(e,t,n){return J.access(e,t,n)},_removeData:function(e,t){J.remove(e,t)}}),w.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=K.get(o),1===o.nodeType&&!J.get(o,"hasDataAttrs"))){n=a.length;while(n--)a[n]&&0===(r=a[n].name).indexOf("data-")&&(r=G(r.slice(5)),ne(o,r,i[r]));J.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){K.set(this,e)}):z(this,function(t){var n;if(o&&void 0===t){if(void 0!==(n=K.get(o,e)))return n;if(void 0!==(n=ne(o,e)))return n}else this.each(function(){K.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length<n?w.queue(this[0],e):void 0===t?this:this.each(function(){var n=w.queue(this,e,t);w._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&w.dequeue(this,e)})},dequeue:function(e){return this.each(function(){w.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=w.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=J.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var re=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ie=new RegExp("^(?:([+-])=|)("+re+")([a-z%]*)$","i"),oe=["Top","Right","Bottom","Left"],ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&w.contains(e.ownerDocument,e)&&"none"===w.css(e,"display")},se=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i};function ue(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return w.css(e,t,"")},u=s(),l=n&&n[3]||(w.cssNumber[t]?"":"px"),c=(w.cssNumber[t]||"px"!==l&&+u)&&ie.exec(w.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)w.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,w.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var le={};function ce(e){var t,n=e.ownerDocument,r=e.nodeName,i=le[r];return i||(t=n.body.appendChild(n.createElement(r)),i=w.css(t,"display"),t.parentNode.removeChild(t),"none"===i&&(i="block"),le[r]=i,i)}function fe(e,t){for(var n,r,i=[],o=0,a=e.length;o<a;o++)(r=e[o]).style&&(n=r.style.display,t?("none"===n&&(i[o]=J.get(r,"display")||null,i[o]||(r.style.display="")),""===r.style.display&&ae(r)&&(i[o]=ce(r))):"none"!==n&&(i[o]="none",J.set(r,"display",n)));for(o=0;o<a;o++)null!=i[o]&&(e[o].style.display=i[o]);return e}w.fn.extend({show:function(){return fe(this,!0)},hide:function(){return fe(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?w(this).show():w(this).hide()})}});var pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n<r;n++)J.set(e[n],"globalEval",!t||J.get(t[n],"globalEval"))}var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===x(o))w.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+w.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;w.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&w.inArray(o,r)>-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="<textarea>x</textarea>",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n<arguments.length;n++)u[n]=arguments[n];if(t.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,t)){s=w.event.handlers.call(this,t,l),n=0;while((o=s[n++])&&!t.isPropagationStopped()){t.currentTarget=o.elem,r=0;while((a=o.handlers[r++])&&!t.isImmediatePropagationStopped())t.rnamespace&&!t.rnamespace.test(a.namespace)||(t.handleObj=a,t.data=a.data,void 0!==(i=((w.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u))&&!1===(t.result=i)&&(t.preventDefault(),t.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,t),t.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&e.button>=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?w(i,this).index(l)>-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(e,t){Object.defineProperty(w.Event.prototype,e,{enumerable:!0,configurable:!0,get:g(t)?function(){if(this.originalEvent)return t(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[e]},set:function(t){Object.defineProperty(this,e,{enumerable:!0,configurable:!0,writable:!0,value:t})}})},fix:function(e){return e[w.expando]?e:new w.Event(e)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==Se()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===Se()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&N(this,"input"))return this.click(),!1},_default:function(e){return N(e.target,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},w.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},w.Event=function(e,t){if(!(this instanceof w.Event))return new w.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ee:ke,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&w.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[w.expando]=!0},w.Event.prototype={constructor:w.Event,isDefaultPrevented:ke,isPropagationStopped:ke,isImmediatePropagationStopped:ke,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ee,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ee,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ee,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},w.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&we.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Te.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},w.event.addProp),w.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,t){w.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||w.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),w.fn.extend({on:function(e,t,n,r){return De(this,e,t,n,r)},one:function(e,t,n,r){return De(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,w(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=ke),this.each(function(){w.event.remove(this,e,n,t)})}});var Ne=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/<script|<style|<link/i,je=/checked\s*(?:[^=]|=\s*.checked.)/i,qe=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n<r;n++)w.event.add(t,i,l[i][n])}K.hasData(e)&&(s=K.access(e),u=w.extend({},s),K.set(t,u))}}function Me(e,t){var n=t.nodeName.toLowerCase();"input"===n&&pe.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function Re(e,t,n,r){t=a.apply([],t);var i,o,s,u,l,c,f=0,p=e.length,d=p-1,y=t[0],v=g(y);if(v||p>1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f<p;f++)l=i,f!==d&&(l=w.clone(l,!0,!0),u&&w.merge(s,ye(l,"script"))),n.call(e[f],l,f);if(u)for(c=s[s.length-1].ownerDocument,w.map(s,Oe),f=0;f<u;f++)l=s[f],he.test(l.type||"")&&!J.access(l,"globalEval")&&w.contains(c,l)&&(l.src&&"module"!==(l.type||"").toLowerCase()?w._evalUrl&&w._evalUrl(l.src):m(l.textContent.replace(qe,""),c,l))}return e}function Ie(e,t,n){for(var r,i=t?w.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||w.cleanData(ye(r)),r.parentNode&&(n&&w.contains(r.ownerDocument,r)&&ve(ye(r,"script")),r.parentNode.removeChild(r));return e}w.extend({htmlPrefilter:function(e){return e.replace(Ne,"<$1></$2>")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r<i;r++)Me(o[r],a[r]);if(t)if(n)for(o=o||ye(e),a=a||ye(s),r=0,i=o.length;r<i;r++)Pe(o[r],a[r]);else Pe(e,s);return(a=ye(s,"script")).length>0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(w.cleanData(ye(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return Re(this,arguments,function(t){var n=this.parentNode;w.inArray(this,e)<0&&(w.cleanData(ye(this)),n&&n.replaceChild(t,this))},e)}}),w.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){w.fn[e]=function(e){for(var n,r=[],i=w(e),o=i.length-1,a=0;a<=o;a++)n=a===o?this:this.clone(!0),w(i[a])[t](n),s.apply(r,n.get());return this.pushStack(r)}});var We=new RegExp("^("+re+")(?!px)[a-z%]+$","i"),$e=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},Be=new RegExp(oe.join("|"),"i");!function(){function t(){if(c){l.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",c.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",be.appendChild(l).appendChild(c);var t=e.getComputedStyle(c);i="1%"!==t.top,u=12===n(t.marginLeft),c.style.right="60%",s=36===n(t.right),o=36===n(t.width),c.style.position="absolute",a=36===c.offsetWidth||"absolute",be.removeChild(l),c=null}}function n(e){return Math.round(parseFloat(e))}var i,o,a,s,u,l=r.createElement("div"),c=r.createElement("div");c.style&&(c.style.backgroundClip="content-box",c.cloneNode(!0).style.backgroundClip="",h.clearCloneStyle="content-box"===c.style.backgroundClip,w.extend(h,{boxSizingReliable:function(){return t(),o},pixelBoxStyles:function(){return t(),s},pixelPosition:function(){return t(),i},reliableMarginLeft:function(){return t(),u},scrollboxSize:function(){return t(),a}}))}();function Fe(e,t,n){var r,i,o,a,s=e.style;return(n=n||$e(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||w.contains(e.ownerDocument,e)||(a=w.style(e,t)),!h.pixelBoxStyles()&&We.test(a)&&Be.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function _e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}var ze=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ue={position:"absolute",visibility:"hidden",display:"block"},Ve={letterSpacing:"0",fontWeight:"400"},Ge=["Webkit","Moz","ms"],Ye=r.createElement("div").style;function Qe(e){if(e in Ye)return e;var t=e[0].toUpperCase()+e.slice(1),n=Ge.length;while(n--)if((e=Ge[n]+t)in Ye)return e}function Je(e){var t=w.cssProps[e];return t||(t=w.cssProps[e]=Qe(e)||e),t}function Ke(e,t,n){var r=ie.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ze(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=w.css(e,n+oe[a],!0,i)),r?("content"===n&&(u-=w.css(e,"padding"+oe[a],!0,i)),"margin"!==n&&(u-=w.css(e,"border"+oe[a]+"Width",!0,i))):(u+=w.css(e,"padding"+oe[a],!0,i),"padding"!==n?u+=w.css(e,"border"+oe[a]+"Width",!0,i):s+=w.css(e,"border"+oe[a]+"Width",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a<i;a++)o[t[a]]=w.css(e,t[a],!1,r);return o}return void 0!==n?w.style(e,t,n):w.css(e,t)},e,t,arguments.length>1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ct(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),y=J.get(e,"fxshow");n.queue||(null==(a=w._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,w.queue(e,"fx").length||a.empty.fire()})}));for(r in t)if(i=t[r],it.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!y||void 0===y[r])continue;g=!0}d[r]=y&&y[r]||w.style(e,r)}if((u=!w.isEmptyObject(t))||!w.isEmptyObject(d)){f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=y&&y.display)&&(l=J.get(e,"display")),"none"===(c=w.css(e,"display"))&&(l?c=l:(fe([e],!0),l=e.style.display||l,c=w.css(e,"display"),fe([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===w.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1;for(r in d)u||(y?"hidden"in y&&(g=y.hidden):y=J.access(e,"fxshow",{display:l}),o&&(y.hidden=!g),g&&fe([e],!0),p.done(function(){g||fe([e]),J.remove(e,"fxshow");for(r in d)w.style(e,r,d[r])})),u=lt(g?y[r]:0,r,p),r in y||(y[r]=u.start,g&&(u.end=u.start,u.start=0))}}function ft(e,t){var n,r,i,o,a;for(n in e)if(r=G(n),i=t[r],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=w.cssHooks[r])&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function pt(e,t,n){var r,i,o=0,a=pt.prefilters.length,s=w.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=nt||st(),n=Math.max(0,l.startTime+l.duration-t),r=1-(n/l.duration||0),o=0,a=l.tweens.length;o<a;o++)l.tweens[o].run(r);return s.notifyWith(e,[l,r,n]),r<1&&a?n:(a||s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:w.extend({},t),opts:w.extend(!0,{specialEasing:{},easing:w.easing._default},n),originalProperties:t,originalOptions:n,startTime:nt||st(),duration:n.duration,tweens:[],createTween:function(t,n){var r=w.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(ft(c,l.opts.specialEasing);o<a;o++)if(r=pt.prefilters[o].call(l,e,c,l.opts))return g(r.stop)&&(w._queueHooks(l.elem,l.opts.queue).stop=r.stop.bind(r)),r;return w.map(c,lt,l),g(l.opts.start)&&l.opts.start.call(e,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),w.fx.timer(w.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l}w.Animation=w.extend(pt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return ue(n.elem,e,ie.exec(t),n),n}]},tweener:function(e,t){g(e)?(t=e,e=["*"]):e=e.match(M);for(var n,r=0,i=e.length;r<i;r++)n=e[r],pt.tweeners[n]=pt.tweeners[n]||[],pt.tweeners[n].unshift(t)},prefilters:[ct],prefilter:function(e,t){t?pt.prefilters.unshift(e):pt.prefilters.push(e)}}),w.speed=function(e,t,n){var r=e&&"object"==typeof e?w.extend({},e):{complete:n||!n&&t||g(e)&&e,duration:e,easing:n&&t||t&&!g(t)&&t};return w.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in w.fx.speeds?r.duration=w.fx.speeds[r.duration]:r.duration=w.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){g(r.old)&&r.old.call(this),r.queue&&w.dequeue(this,r.queue)},r},w.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=w.isEmptyObject(e),o=w.speed(t,n,r),a=function(){var t=pt(this,w.extend({},e),o);(i||J.get(this,"finish"))&&t.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=void 0),t&&!1!==e&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=w.timers,a=J.get(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&ot.test(i)&&r(a[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||w.dequeue(this,e)})},finish:function(e){return!1!==e&&(e=e||"fx"),this.each(function(){var t,n=J.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=w.timers,a=r?r.length:0;for(n.finish=!0,w.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),w.each(["toggle","show","hide"],function(e,t){var n=w.fn[t];w.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ut(t,!0),e,r,i)}}),w.each({slideDown:ut("show"),slideUp:ut("hide"),slideToggle:ut("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){w.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),w.timers=[],w.fx.tick=function(){var e,t=0,n=w.timers;for(nt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||w.fx.stop(),nt=void 0},w.fx.timer=function(e){w.timers.push(e),w.fx.start()},w.fx.interval=13,w.fx.start=function(){rt||(rt=!0,at())},w.fx.stop=function(){rt=null},w.fx.speeds={slow:600,fast:200,_default:400},w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement("input"),t=r.createElement("select").appendChild(r.createElement("option"));e.type="checkbox",h.checkOn=""!==e.value,h.optSelected=t.selected,(e=r.createElement("input")).value="t",e.type="radio",h.radioValue="t"===e.value}();var dt,ht=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return z(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!N(n.parentNode,"optgroup"))){if(t=w(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=w.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=w.inArray(w.valHooks.option.get(r),o)>-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("<script>").prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&o("error"===e.type?404:200,e.type)}),r.head.appendChild(t[0])},abort:function(){n&&n()}}}});var Yt=[],Qt=/(=)\?(?=&|$)|\?\?/;w.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Yt.pop()||w.expando+"_"+Et++;return this[e]=!0,e}}),w.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=!1!==t.jsonp&&(Qt.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&Qt.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=g(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(Qt,"$1"+i):!1!==t.jsonp&&(t.url+=(kt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||w.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?w(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,Yt.push(i)),a&&g(o)&&o(a[0]),a=o=void 0}),"script"}),h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument("").body;return e.innerHTML="<form></form><form></form>",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument("")).createElement("base")).href=r.location.href,t.head.appendChild(i)):t=r),o=A.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=xe([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=vt(e.slice(s)),e=e.slice(0,s)),g(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&w.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?w("<div>").append(w.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},w.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){w.fn[t]=function(e){return this.on(t,e)}}),w.expr.pseudos.animated=function(e){return w.grep(w.timers,function(t){return e===t.elem}).length},w.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=w.css(e,"position"),f=w(e),p={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=w.css(e,"top"),u=w.css(e,"left"),(l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),g(t)&&(t=t.call(e,n,w.extend({},s))),null!=t.top&&(p.top=t.top-s.top+a),null!=t.left&&(p.left=t.left-s.left+i),"using"in t?t.using.call(e,p):f.css(p)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===w.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===w.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,"borderTopWidth",!0),i.left+=w.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-w.css(r,"marginTop",!0),left:t.left-i.left-w.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===w.css(e,"position"))e=e.offsetParent;return e||be})}}),w.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;w.fn[e]=function(r){return z(this,function(e,r,i){var o;if(y(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each(["top","left"],function(e,t){w.cssHooks[t]=_e(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),We.test(n)?w(e).position()[t]+"px":n})}),w.each({Height:"height",Width:"width"},function(e,t){w.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),s=n||(!0===i||!0===o?"margin":"border");return z(this,function(t,n,i){var o;return y(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?w.css(t,n,s):w.style(t,n,i,s)},t,a?i:void 0,a)}})}),w.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),w.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=N,w.isFunction=g,w.isWindow=y,w.camelCase=G,w.type=x,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return w});var Jt=e.jQuery,Kt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=Kt),t&&e.jQuery===w&&(e.jQuery=Jt),w},t||(e.jQuery=e.$=w),w});
diff --git a/share/static/js/receive-noscript.js b/share/static/js/receive-noscript.js
new file mode 100644
index 00000000..0f4ac1bc
--- /dev/null
+++ b/share/static/js/receive-noscript.js
@@ -0,0 +1,2 @@
+// Hide the noscript div, because our javascript is executing
+document.getElementById('noscript').style.display = 'none';
diff --git a/share/static/js/receive.js b/share/static/js/receive.js
new file mode 100644
index 00000000..c29c726c
--- /dev/null
+++ b/share/static/js/receive.js
@@ -0,0 +1,127 @@
+$(function(){
+ // Add a flash message
+ var flash = function(category, message) {
+ $('#flashes').append($('<li>').addClass(category).text(message));
+ };
+
+ // Intercept submitting the form
+ $('#send').submit(function(event){
+ event.preventDefault();
+
+ // Create form data, and list of filenames
+ var files = $('#file-select').get(0).files;
+ var filenames = [];
+ var formData = new FormData();
+ for(var i = 0; i < files.length; i++) {
+ var file = files[i];
+ filenames.push(file.name);
+ formData.append('file[]', file, file.name);
+ }
+
+ // Reset the upload form
+ $('#send').get(0).reset();
+
+ // Don't use jQuery for ajax request, because the upload progress event doesn't
+ // have access to the the XMLHttpRequest object
+ var ajax = new XMLHttpRequest();
+
+ ajax.upload.addEventListener('progress', function(event){
+ // Update progress bar for this specific upload
+ if(event.lengthComputable) {
+ $('progress', ajax.$upload_div).attr({
+ value: event.loaded,
+ max: event.total,
+ });
+ }
+
+ // If it's finished sending all data to the first Tor node, remove cancel button
+ // and update the status
+ if(event.loaded == event.total) {
+ $('.cancel', ajax.$upload_div).remove();
+ $('.upload-status', ajax.$upload_div).html('<img src="/static/img/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...');
+ }
+ }, false);
+
+ ajax.addEventListener('load', function(event){
+ // Remove the upload div
+ ajax.$upload_div.remove();
+
+ // Parse response
+ try {
+ var response = JSON.parse(ajax.response);
+
+ // The 'new_body' response replaces the whole HTML document and ends
+ if('new_body' in response) {
+ $('body').html(response['new_body']);
+ return;
+ }
+
+ // Show error flashes
+ if('error_flashes' in response) {
+ for(var i=0; i<response['error_flashes'].length; i++) {
+ flash('error', response['error_flashes'][i]);
+ }
+ }
+
+ // Show info flashes
+ if('info_flashes' in response) {
+ for(var i=0; i<response['info_flashes'].length; i++) {
+ flash('info', response['info_flashes'][i]);
+ }
+ }
+ } catch(e) {
+ flash('error', 'Invalid response from server: '+data);
+ }
+ }, false);
+
+ ajax.addEventListener('error', function(event){
+ flash('error', 'Error uploading: '+filenames.join(', '));
+
+ // Remove the upload div
+ ajax.$upload_div.remove()
+ }, false);
+
+ ajax.addEventListener('abort', function(event){
+ flash('error', 'Upload aborted: '+filenames.join(', '));
+ }, false);
+
+ // Make the upload div
+
+ /* The DOM for an upload looks something like this:
+ <div class="upload">
+ <div class="upload-meta">
+ <input class="cancel" type="button" value="Cancel" />
+ <div class="upload-filename">educational-video.mp4, secret-plans.pdf</div>
+ <div class="upload-status">Sending to first Tor node ...</div>
+ </div>
+ <progress value="25" max="100"></progress>
+ </div> */
+ var $progress = $('<progress>').attr({ value: '0', max: 100 });
+ var $cancel_button = $('<input>').addClass('cancel').attr({ type: 'button', value: 'Cancel' });
+ var $upload_filename = $('<div>').addClass('upload-filename').text(filenames.join(', '));
+ var $upload_status = $('<div>').addClass('upload-status').text('Sending data to initial Tor node ...');
+
+ var $upload_div = $('<div>')
+ .addClass('upload')
+ .append(
+ $('<div>').addClass('upload-meta')
+ .append($cancel_button)
+ .append($upload_filename)
+ .append($upload_status)
+ )
+ .append($progress);
+
+ $cancel_button.click(function(){
+ // Abort the upload, and remove the upload div
+ ajax.abort();
+ $upload_div.remove()
+ });
+
+ ajax.$upload_div = $upload_div;
+ $('#uploads').append($upload_div);
+
+ // Send the request
+ ajax.open('POST', window.location.pathname.replace(/\/$/, '') + '/upload-ajax', true);
+ ajax.send(formData);
+ });
+});
diff --git a/share/static/js/send.js b/share/static/js/send.js
new file mode 100644
index 00000000..43e9892d
--- /dev/null
+++ b/share/static/js/send.js
@@ -0,0 +1,75 @@
+// Function to convert human-readable sizes back to bytes, for sorting
+function unhumanize(text) {
+ var powers = {'b': 0, 'k': 1, 'm': 2, 'g': 3, 't': 4};
+ var regex = /(\d+(?:\.\d+)?)\s?(B|K|M|G|T)?/i;
+ var res = regex.exec(text);
+ if(res[2] === undefined) {
+ // Account for alphabetical words (file/dir names)
+ return text;
+ } else {
+ return res[1] * Math.pow(1024, powers[res[2].toLowerCase()]);
+ }
+}
+function sortTable(n) {
+ var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
+ table = document.getElementById("file-list");
+ switching = true;
+ // Set the sorting direction to ascending:
+ dir = "asc";
+ /* Make a loop that will continue until
+ no switching has been done: */
+ while (switching) {
+ // Start by saying: no switching is done:
+ switching = false;
+ rows = table.getElementsByTagName("TR");
+ /* Loop through all table rows (except the
+ first, which contains table headers): */
+ for (i = 1; i < (rows.length - 1); i++) {
+ // Start by saying there should be no switching:
+ shouldSwitch = false;
+ /* Get the two elements you want to compare,
+ one from current row and one from the next: */
+ x = rows[i].getElementsByTagName("TD")[n];
+ y = rows[i + 1].getElementsByTagName("TD")[n];
+ /* Check if the two rows should switch place,
+ based on the direction, asc or desc: */
+ if (dir == "asc") {
+ if (unhumanize(x.innerHTML.toLowerCase()) > unhumanize(y.innerHTML.toLowerCase())) {
+ // If so, mark as a switch and break the loop:
+ shouldSwitch= true;
+ break;
+ }
+ } else if (dir == "desc") {
+ if (unhumanize(x.innerHTML.toLowerCase()) < unhumanize(y.innerHTML.toLowerCase())) {
+ // If so, mark as a switch and break the loop:
+ shouldSwitch= true;
+ break;
+ }
+ }
+ }
+ if (shouldSwitch) {
+ /* If a switch has been marked, make the switch
+ and mark that a switch has been done: */
+ rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
+ switching = true;
+ // Each time a switch is done, increase this count by 1:
+ switchcount ++;
+ } else {
+ /* If no switching has been done AND the direction is "asc",
+ set the direction to "desc" and run the while loop again. */
+ if (switchcount == 0 && dir == "asc") {
+ dir = "desc";
+ switching = true;
+ }
+ }
+ }
+}
+
+
+// Set click handlers
+document.getElementById("filename-header").addEventListener("click", function(){
+ sortTable(0);
+});
+document.getElementById("size-header").addEventListener("click", function(){
+ sortTable(1);
+});
diff --git a/share/templates/403.html b/share/templates/403.html
new file mode 100644
index 00000000..df81f3e7
--- /dev/null
+++ b/share/templates/403.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare: 403 Forbidden</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">You are not allowed to perform that action at this time.</p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/share/templates/404.html b/share/templates/404.html
new file mode 100644
index 00000000..264ca517
--- /dev/null
+++ b/share/templates/404.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare: 404 Not Found</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">404 Not Found</p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/share/templates/denied.html b/share/templates/denied.html
new file mode 100644
index 00000000..5d411d62
--- /dev/null
+++ b/share/templates/denied.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ </head>
+ <body>
+ <p>OnionShare download in progress</p>
+ </body>
+</html>
diff --git a/share/templates/receive.html b/share/templates/receive.html
new file mode 100644
index 00000000..a557a967
--- /dev/null
+++ b/share/templates/receive.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+
+ <header class="clearfix">
+ <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <h1>OnionShare</h1>
+ </header>
+
+ <div class="upload-wrapper">
+ <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+
+ <p class="upload-header">Send Files</p>
+ <p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
+
+ <form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}">
+ <p><input type="file" id="file-select" name="file[]" multiple /></p>
+ <p><button type="submit" id="send-button" class="button">Send Files</button></p>
+ </form>
+
+ <div id="uploads"></div>
+
+ <div>
+ <ul id="flashes" class="flashes">
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% if messages %}
+ {% for category, message in messages %}
+ <li class="{{ category }}">{{ message }}</li>
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+ </ul>
+ </div>
+
+ <!--
+ We are not using a <noscript> tag because it only works when the security slider is set to
+ Safest, not Safer: https://trac.torproject.org/projects/tor/ticket/29506
+ -->
+ <div id="noscript">
+ <p>
+ <strong>Warning:</strong> Due to a bug in Tor Browser and Firefox, uploads
+ sometimes never finish. To upload reliably, either set your Tor Browser
+ <a rel="noreferrer" target="_blank" href="https://tb-manual.torproject.org/en-US/security-slider/">security slider</a>
+ to Standard or
+ <a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
+ </div>
+ <script src="/static/js/receive-noscript.js"></script>
+
+ </div>
+ <script src="/static/js/jquery-3.3.1.min.js"></script>
+ <script src="/static/js/receive.js"></script>
+ </body>
+</html>
diff --git a/share/templates/receive_noscript_xss.html b/share/templates/receive_noscript_xss.html
new file mode 100644
index 00000000..bf846b03
--- /dev/null
+++ b/share/templates/receive_noscript_xss.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+
+ <header class="clearfix">
+ <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <h1>OnionShare</h1>
+ </header>
+
+ <div class="disable-noscript-xss-wrapper">
+ <h3>Disable your Tor Browser's NoScript XSS setting</h3>
+
+ <p>If your security slider is set to Safest, JavaScript is disabled so XSS vulnerabilities won't affect you,
+ which makes it safe to disable NoScript's XSS protections.</p>
+
+ <p>Here is how to disable this setting:</p>
+
+ <ol>
+ <li>Click the menu icon in the top-right of Tor Browser and open "Add-ons"</li>
+ <li>Next to the NoScript add-on, click the "Preferences" button</li>
+ <li>Switch to the "Advanced" tab</li>
+ <li>Uncheck "Sanitize cross-site suspicious requests"</li>
+ </ol>
+
+ <p>If you'd like to learn technical details about this issue, check
+ <a rel="noreferrer" href="https://github.com/micahflee/onionshare/issues/899">this issue</a>
+ on GitHub.</p>
+ </div>
+ </body>
+</html>
diff --git a/share/templates/send.html b/share/templates/send.html
new file mode 100644
index 00000000..e7e1fde0
--- /dev/null
+++ b/share/templates/send.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ <meta name="onionshare-filename" content="{{ filename }}">
+ <meta name="onionshare-filesize" content="{{ filesize }}">
+ </head>
+ <body>
+
+ <header class="clearfix">
+ <div class="right">
+ <ul>
+ <li>Total size: <strong>{{ filesize_human }}</strong> {% if is_zipped %} (compressed){% endif %}</li>
+ {% if slug %}
+ <li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
+ {% else %}
+ <li><a class="button" href='/download'>Download Files</a></li>
+ {% endif %}
+ </ul>
+ </div>
+ <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <h1>OnionShare</h1>
+ </header>
+
+ <table class="file-list" id="file-list">
+ <tr>
+ <th id="filename-header">Filename</th>
+ <th id="size-header">Size</th>
+ <th></th>
+ </tr>
+ {% for info in file_info.dirs %}
+ <tr>
+ <td>
+ <img width="30" height="30" title="" alt="" src="/static/img/web_folder.png" />
+ {{ info.basename }}
+ </td>
+ <td>{{ info.size_human }}</td>
+ <td></td>
+ </tr>
+ {% endfor %}
+ {% for info in file_info.files %}
+ <tr>
+ <td>
+ <img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
+ {{ info.basename }}
+ </td>
+ <td>{{ info.size_human }}</td>
+ <td></td>
+ </tr>
+ {% endfor %}
+ </table>
+ <script src="/static/js/send.js"></script>
+ </body>
+</html>
diff --git a/share/templates/thankyou.html b/share/templates/thankyou.html
new file mode 100644
index 00000000..64c8b369
--- /dev/null
+++ b/share/templates/thankyou.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare is closed</title>
+ <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="/static/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <header class="clearfix">
+ <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <h1>OnionShare</h1>
+ </header>
+
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">Thank you for using OnionShare</p>
+ <p class="info-description">You may now close this window.</p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/share/torrc_template b/share/torrc_template
index 464adf32..8ac9e1ef 100644
--- a/share/torrc_template
+++ b/share/torrc_template
@@ -1,6 +1,5 @@
DataDirectory {{data_directory}}
SocksPort {{socks_port}}
-ControlSocket {{control_socket}}
CookieAuthentication 1
CookieAuthFile {{cookie_auth_file}}
AvoidDiskWrites 1
diff --git a/share/torrc_template-windows b/share/torrc_template-windows
deleted file mode 100644
index 38a5bf1e..00000000
--- a/share/torrc_template-windows
+++ /dev/null
@@ -1,9 +0,0 @@
-DataDirectory {{data_directory}}
-SocksPort {{socks_port}}
-ControlPort {{control_port}}
-CookieAuthentication 1
-CookieAuthFile {{cookie_auth_file}}
-AvoidDiskWrites 1
-Log notice stdout
-GeoIPFile {{geo_ip_file}}
-GeoIPv6File {{geo_ipv6_file}}
diff --git a/share/version.txt b/share/version.txt
index 1892b926..cd5ac039 100644
--- a/share/version.txt
+++ b/share/version.txt
@@ -1 +1 @@
-1.3.2
+2.0
diff --git a/stdeb.cfg b/stdeb.cfg
index 334502c0..de6c52c2 100644
--- a/stdeb.cfg
+++ b/stdeb.cfg
@@ -1,6 +1,6 @@
[DEFAULT]
Package3: onionshare
-Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy
-Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5
-Suite: xenial
-X-Python3-Version: >= 3.4
+Depends3: python3, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy
+Build-Depends: python3, python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy
+Suite: bionic
+X-Python3-Version: >= 3.5.3
diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py
new file mode 100644
index 00000000..e4b3d4c9
--- /dev/null
+++ b/tests/GuiBaseTest.py
@@ -0,0 +1,321 @@
+import json
+import os
+import requests
+import shutil
+import socket
+import socks
+
+from PyQt5 import QtCore, QtTest
+
+from onionshare import strings
+from onionshare.common import Common
+from onionshare.settings import Settings
+from onionshare.onion import Onion
+from onionshare.web import Web
+from onionshare_gui import Application, OnionShare, OnionShareGui
+from onionshare_gui.mode.share_mode import ShareMode
+from onionshare_gui.mode.receive_mode import ReceiveMode
+
+
+class GuiBaseTest(object):
+ @staticmethod
+ def set_up(test_settings):
+ '''Create GUI with given settings'''
+ # Create our test file
+ testfile = open('/tmp/test.txt', 'w')
+ testfile.write('onionshare')
+ testfile.close()
+
+ # Create a test dir and files
+ if not os.path.exists('/tmp/testdir'):
+ testdir = os.mkdir('/tmp/testdir')
+ testfile = open('/tmp/testdir/test', 'w')
+ testfile.write('onionshare')
+ testfile.close()
+
+ common = Common()
+ common.settings = Settings(common)
+ common.define_css()
+ strings.load_strings(common)
+
+ # Get all of the settings in test_settings
+ test_settings['data_dir'] = '/tmp/OnionShare'
+ for key, val in common.settings.default_settings.items():
+ if key not in test_settings:
+ test_settings[key] = val
+
+ # Start the Onion
+ testonion = Onion(common)
+ global qtapp
+ qtapp = Application(common)
+ app = OnionShare(common, testonion, True, 0)
+
+ web = Web(common, False, True)
+ open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
+
+ gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', True)
+ return gui
+
+ @staticmethod
+ def tear_down():
+ '''Clean up after tests'''
+ try:
+ os.remove('/tmp/test.txt')
+ os.remove('/tmp/settings.json')
+ os.remove('/tmp/large_file')
+ os.remove('/tmp/download.zip')
+ os.remove('/tmp/webpage')
+ shutil.rmtree('/tmp/testdir')
+ shutil.rmtree('/tmp/OnionShare')
+ except:
+ pass
+
+
+ def gui_loaded(self):
+ '''Test that the GUI actually is shown'''
+ self.assertTrue(self.gui.show)
+
+
+ def windowTitle_seen(self):
+ '''Test that the window title is OnionShare'''
+ self.assertEqual(self.gui.windowTitle(), 'OnionShare')
+
+
+ def settings_button_is_visible(self):
+ '''Test that the settings button is visible'''
+ self.assertTrue(self.gui.settings_button.isVisible())
+
+
+ def settings_button_is_hidden(self):
+ '''Test that the settings button is hidden when the server starts'''
+ self.assertFalse(self.gui.settings_button.isVisible())
+
+
+ def server_status_bar_is_visible(self):
+ '''Test that the status bar is visible'''
+ self.assertTrue(self.gui.status_bar.isVisible())
+
+
+ def click_mode(self, mode):
+ '''Test that we can switch Mode by clicking the button'''
+ if type(mode) == ReceiveMode:
+ QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton)
+ self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE)
+ if type(mode) == ShareMode:
+ QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton)
+ self.assertTrue(self.gui.mode, self.gui.MODE_SHARE)
+
+
+ def click_toggle_history(self, mode):
+ '''Test that we can toggle Download or Upload history by clicking the toggle button'''
+ currently_visible = mode.history.isVisible()
+ QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
+ self.assertEqual(mode.history.isVisible(), not currently_visible)
+
+
+ def history_indicator(self, mode, public_mode):
+ '''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
+ # Make sure history is toggled off
+ if mode.history.isVisible():
+ QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
+ self.assertFalse(mode.history.isVisible())
+
+ # Indicator should not be visible yet
+ self.assertFalse(mode.toggle_history.indicator_label.isVisible())
+
+ if type(mode) == ReceiveMode:
+ # Upload a file
+ files = {'file[]': open('/tmp/test.txt', 'rb')}
+ if not public_mode:
+ path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug)
+ else:
+ path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
+ response = requests.post(path, files=files)
+ QtTest.QTest.qWait(2000)
+
+ if type(mode) == ShareMode:
+ # Download files
+ if public_mode:
+ url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
+ else:
+ url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug)
+ r = requests.get(url)
+ QtTest.QTest.qWait(2000)
+
+ # Indicator should be visible, have a value of "1"
+ self.assertTrue(mode.toggle_history.indicator_label.isVisible())
+ self.assertEqual(mode.toggle_history.indicator_label.text(), "1")
+
+ # Toggle history back on, indicator should be hidden again
+ QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
+ self.assertFalse(mode.toggle_history.indicator_label.isVisible())
+
+
+ def history_is_not_visible(self, mode):
+ '''Test that the History section is not visible'''
+ self.assertFalse(mode.history.isVisible())
+
+
+ def history_is_visible(self, mode):
+ '''Test that the History section is visible'''
+ self.assertTrue(mode.history.isVisible())
+
+
+ def server_working_on_start_button_pressed(self, mode):
+ '''Test we can start the service'''
+ # Should be in SERVER_WORKING state
+ QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
+ self.assertEqual(mode.server_status.status, 1)
+
+
+ def server_status_indicator_says_starting(self, mode):
+ '''Test that the Server Status indicator shows we are Starting'''
+ self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working'))
+
+
+ def server_is_started(self, mode, startup_time=2000):
+ '''Test that the server has started'''
+ QtTest.QTest.qWait(startup_time)
+ # Should now be in SERVER_STARTED state
+ self.assertEqual(mode.server_status.status, 2)
+
+
+ def web_server_is_running(self):
+ '''Test that the web server has started'''
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
+
+
+ def have_a_slug(self, mode, public_mode):
+ '''Test that we have a valid slug'''
+ if not public_mode:
+ self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)')
+ else:
+ self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)')
+
+
+ def url_description_shown(self, mode):
+ '''Test that the URL label is showing'''
+ self.assertTrue(mode.server_status.url_description.isVisible())
+
+
+ def have_copy_url_button(self, mode, public_mode):
+ '''Test that the Copy URL button is shown and that the clipboard is correct'''
+ self.assertTrue(mode.server_status.copy_url_button.isVisible())
+
+ QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton)
+ clipboard = self.gui.qtapp.clipboard()
+ if public_mode:
+ self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port))
+ else:
+ self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug))
+
+
+ def server_status_indicator_says_started(self, mode):
+ '''Test that the Server Status indicator shows we are started'''
+ if type(mode) == ReceiveMode:
+ self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started'))
+ if type(mode) == ShareMode:
+ self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started'))
+
+
+ def web_page(self, mode, string, public_mode):
+ '''Test that the web page contains a string'''
+ s = socks.socksocket()
+ s.settimeout(60)
+ s.connect(('127.0.0.1', self.gui.app.port))
+
+ if not public_mode:
+ path = '/{}'.format(mode.server_status.web.slug)
+ else:
+ path = '/'
+
+ http_request = 'GET {} HTTP/1.0\r\n'.format(path)
+ http_request += 'Host: 127.0.0.1\r\n'
+ http_request += '\r\n'
+ s.sendall(http_request.encode('utf-8'))
+
+ with open('/tmp/webpage', 'wb') as file_to_write:
+ while True:
+ data = s.recv(1024)
+ if not data:
+ break
+ file_to_write.write(data)
+ file_to_write.close()
+
+ f = open('/tmp/webpage')
+ self.assertTrue(string in f.read())
+ f.close()
+
+
+ def history_widgets_present(self, mode):
+ '''Test that the relevant widgets are present in the history view after activity has taken place'''
+ self.assertFalse(mode.history.empty.isVisible())
+ self.assertTrue(mode.history.not_empty.isVisible())
+
+
+ def counter_incremented(self, mode, count):
+ '''Test that the counter has incremented'''
+ self.assertEqual(mode.history.completed_count, count)
+
+
+ def server_is_stopped(self, mode, stay_open):
+ '''Test that the server stops when we click Stop'''
+ if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open):
+ QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
+ self.assertEqual(mode.server_status.status, 0)
+
+
+ def web_server_is_stopped(self):
+ '''Test that the web server also stopped'''
+ QtTest.QTest.qWait(2000)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ # We should be closed by now. Fail if not!
+ self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
+
+
+ def server_status_indicator_says_closed(self, mode, stay_open):
+ '''Test that the Server Status indicator shows we closed'''
+ if type(mode) == ReceiveMode:
+ self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped'))
+ if type(mode) == ShareMode:
+ if stay_open:
+ self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped'))
+ else:
+ self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically'))
+
+
+ # Auto-stop timer tests
+ def set_timeout(self, mode, timeout):
+ '''Test that the timeout can be set'''
+ timer = QtCore.QDateTime.currentDateTime().addSecs(timeout)
+ mode.server_status.shutdown_timeout.setDateTime(timer)
+ self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer)
+
+
+ def timeout_widget_hidden(self, mode):
+ '''Test that the timeout widget is hidden when share has started'''
+ self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible())
+
+
+ def server_timed_out(self, mode, wait):
+ '''Test that the server has timed out after the timer ran out'''
+ QtTest.QTest.qWait(wait)
+ # We should have timed out now
+ self.assertEqual(mode.server_status.status, 0)
+
+ # Hack to close an Alert dialog that would otherwise block tests
+ def accept_dialog(self):
+ window = self.gui.qtapp.activeWindow()
+ if window:
+ window.close()
+
+ # 'Grouped' tests follow from here
+
+ def run_all_common_setup_tests(self):
+ self.gui_loaded()
+ self.windowTitle_seen()
+ self.settings_button_is_visible()
+ self.server_status_bar_is_visible()
diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py
new file mode 100644
index 00000000..8a03283e
--- /dev/null
+++ b/tests/GuiReceiveTest.py
@@ -0,0 +1,147 @@
+import os
+import requests
+from datetime import datetime, timedelta
+from PyQt5 import QtCore, QtTest
+from .GuiBaseTest import GuiBaseTest
+
+class GuiReceiveTest(GuiBaseTest):
+ def upload_file(self, public_mode, file_to_upload, expected_basename):
+ '''Test that we can upload the file'''
+ files = {'file[]': open(file_to_upload, 'rb')}
+ if not public_mode:
+ path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
+ else:
+ path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
+ response = requests.post(path, files=files)
+ QtTest.QTest.qWait(2000)
+
+ # Make sure the file is within the last 10 seconds worth of filenames
+ exists = False
+ now = datetime.now()
+ for i in range(10):
+ date_dir = now.strftime("%Y-%m-%d")
+ time_dir = now.strftime("%H.%M.%S")
+ receive_mode_dir = os.path.join(self.gui.common.settings.get('data_dir'), date_dir, time_dir)
+ expected_filename = os.path.join(receive_mode_dir, expected_basename)
+ if os.path.exists(expected_filename):
+ exists = True
+ break
+ now = now - timedelta(seconds=1)
+
+ self.assertTrue(exists)
+
+ def upload_file_should_fail(self, public_mode):
+ '''Test that we can't upload the file when permissions are wrong, and expected content is shown'''
+ files = {'file[]': open('/tmp/test.txt', 'rb')}
+ if not public_mode:
+ path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
+ else:
+ path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
+ response = requests.post(path, files=files)
+
+ QtCore.QTimer.singleShot(1000, self.accept_dialog)
+ self.assertTrue('Error uploading, please inform the OnionShare user' in response.text)
+
+ def upload_dir_permissions(self, mode=0o755):
+ '''Manipulate the permissions on the upload dir in between tests'''
+ os.chmod('/tmp/OnionShare', mode)
+
+ def try_public_paths_in_non_public_mode(self):
+ response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port))
+ self.assertEqual(response.status_code, 404)
+ response = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port))
+ self.assertEqual(response.status_code, 404)
+
+ def uploading_zero_files_shouldnt_change_ui(self, mode, public_mode):
+ '''If you submit the receive mode form without selecting any files, the UI shouldn't get updated'''
+ if not public_mode:
+ path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
+ else:
+ path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
+
+ # What were the counts before submitting the form?
+ before_in_progress_count = mode.history.in_progress_count
+ before_completed_count = mode.history.completed_count
+ before_number_of_history_items = len(mode.history.item_list.items)
+
+ # Click submit without including any files a few times
+ response = requests.post(path, files={})
+ response = requests.post(path, files={})
+ response = requests.post(path, files={})
+
+ # The counts shouldn't change
+ self.assertEqual(mode.history.in_progress_count, before_in_progress_count)
+ self.assertEqual(mode.history.completed_count, before_completed_count)
+ self.assertEqual(len(mode.history.item_list.items), before_number_of_history_items)
+
+ def run_receive_mode_sender_closed_tests(self, public_mode):
+ '''Test that the share can be stopped by the sender in receive mode'''
+ if not public_mode:
+ path = 'http://127.0.0.1:{}/{}/close'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
+ else:
+ path = 'http://127.0.0.1:{}/close'.format(self.gui.app.port)
+ response = requests.post(path)
+ self.server_is_stopped(self.gui.receive_mode, False)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.receive_mode, False)
+
+
+ # 'Grouped' tests follow from here
+
+ def run_all_receive_mode_setup_tests(self, public_mode):
+ '''Set up a share in Receive mode and start it'''
+ self.click_mode(self.gui.receive_mode)
+ self.history_is_not_visible(self.gui.receive_mode)
+ self.click_toggle_history(self.gui.receive_mode)
+ self.history_is_visible(self.gui.receive_mode)
+ self.server_working_on_start_button_pressed(self.gui.receive_mode)
+ self.server_status_indicator_says_starting(self.gui.receive_mode)
+ self.settings_button_is_hidden()
+ self.server_is_started(self.gui.receive_mode)
+ self.web_server_is_running()
+ self.have_a_slug(self.gui.receive_mode, public_mode)
+ self.url_description_shown(self.gui.receive_mode)
+ self.have_copy_url_button(self.gui.receive_mode, public_mode)
+ self.server_status_indicator_says_started(self.gui.receive_mode)
+ self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
+
+ def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown):
+ '''Upload files in receive mode and stop the share'''
+ self.run_all_receive_mode_setup_tests(public_mode)
+ if not public_mode:
+ self.try_public_paths_in_non_public_mode()
+ self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
+ self.history_widgets_present(self.gui.receive_mode)
+ self.counter_incremented(self.gui.receive_mode, 1)
+ self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
+ self.counter_incremented(self.gui.receive_mode, 2)
+ self.upload_file(public_mode, '/tmp/testdir/test', 'test')
+ self.counter_incremented(self.gui.receive_mode, 3)
+ self.upload_file(public_mode, '/tmp/testdir/test', 'test')
+ self.counter_incremented(self.gui.receive_mode, 4)
+ self.uploading_zero_files_shouldnt_change_ui(self.gui.receive_mode, public_mode)
+ self.history_indicator(self.gui.receive_mode, public_mode)
+ self.server_is_stopped(self.gui.receive_mode, False)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.receive_mode, False)
+ self.server_working_on_start_button_pressed(self.gui.receive_mode)
+ self.server_is_started(self.gui.receive_mode)
+ self.history_indicator(self.gui.receive_mode, public_mode)
+
+ def run_all_receive_mode_unwritable_dir_tests(self, public_mode, receive_allow_receiver_shutdown):
+ '''Attempt to upload (unwritable) files in receive mode and stop the share'''
+ self.run_all_receive_mode_setup_tests(public_mode)
+ self.upload_dir_permissions(0o400)
+ self.upload_file_should_fail(public_mode)
+ self.server_is_stopped(self.gui.receive_mode, True)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.receive_mode, False)
+ self.upload_dir_permissions(0o755)
+
+ def run_all_receive_mode_timer_tests(self, public_mode):
+ """Auto-stop timer tests in receive mode"""
+ self.run_all_receive_mode_setup_tests(public_mode)
+ self.set_timeout(self.gui.receive_mode, 5)
+ self.timeout_widget_hidden(self.gui.receive_mode)
+ self.server_timed_out(self.gui.receive_mode, 15000)
+ self.web_server_is_stopped()
diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py
new file mode 100644
index 00000000..716bab73
--- /dev/null
+++ b/tests/GuiShareTest.py
@@ -0,0 +1,204 @@
+import os
+import requests
+import socks
+import zipfile
+from PyQt5 import QtCore, QtTest
+from .GuiBaseTest import GuiBaseTest
+
+class GuiShareTest(GuiBaseTest):
+ # Persistence tests
+ def have_same_slug(self, slug):
+ '''Test that we have the same slug'''
+ self.assertEqual(self.gui.share_mode.server_status.web.slug, slug)
+
+ # Share-specific tests
+
+ def file_selection_widget_has_files(self, num=2):
+ '''Test that the number of items in the list is as expected'''
+ self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num)
+
+
+ def deleting_all_files_hides_delete_button(self):
+ '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button'''
+ rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
+ # Delete button should be visible
+ self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
+ # Click delete, delete button should still be visible since we have one more file
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton)
+
+ rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
+ self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton)
+
+ # No more files, the delete button should be hidden
+ self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
+
+
+ def add_a_file_and_delete_using_its_delete_widget(self):
+ '''Test that we can also delete a file by clicking on its [X] widget'''
+ self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton)
+ self.file_selection_widget_has_files(0)
+
+
+ def file_selection_widget_readd_files(self):
+ '''Re-add some files to the list so we can share'''
+ self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
+ self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt')
+ self.file_selection_widget_has_files(2)
+
+
+ def add_large_file(self):
+ '''Add a large file to the share'''
+ size = 1024*1024*155
+ with open('/tmp/large_file', 'wb') as fout:
+ fout.write(os.urandom(size))
+ self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file')
+
+
+ def add_delete_buttons_hidden(self):
+ '''Test that the add and delete buttons are hidden when the server starts'''
+ self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
+ self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
+
+
+ def download_share(self, public_mode):
+ '''Test that we can download the share'''
+ s = socks.socksocket()
+ s.settimeout(60)
+ s.connect(('127.0.0.1', self.gui.app.port))
+
+ if public_mode:
+ path = '/download'
+ else:
+ path = '{}/download'.format(self.gui.share_mode.web.slug)
+
+ http_request = 'GET {} HTTP/1.0\r\n'.format(path)
+ http_request += 'Host: 127.0.0.1\r\n'
+ http_request += '\r\n'
+ s.sendall(http_request.encode('utf-8'))
+
+ with open('/tmp/download.zip', 'wb') as file_to_write:
+ while True:
+ data = s.recv(1024)
+ if not data:
+ break
+ file_to_write.write(data)
+ file_to_write.close()
+
+ zip = zipfile.ZipFile('/tmp/download.zip')
+ QtTest.QTest.qWait(2000)
+ self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
+
+ def hit_404(self, public_mode):
+ '''Test that the server stops after too many 404s, or doesn't when in public_mode'''
+ bogus_path = '/gimme'
+ url = "http://127.0.0.1:{}/{}".format(self.gui.app.port, bogus_path)
+
+ for _ in range(20):
+ r = requests.get(url)
+
+ # A nasty hack to avoid the Alert dialog that blocks the rest of the test
+ if not public_mode:
+ QtCore.QTimer.singleShot(1000, self.accept_dialog)
+
+ # In public mode, we should still be running (no rate-limiting)
+ if public_mode:
+ self.web_server_is_running()
+ # In non-public mode, we should be shut down (rate-limiting)
+ else:
+ self.web_server_is_stopped()
+
+
+ def add_button_visible(self):
+ '''Test that the add button should be visible'''
+ self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
+
+
+ # 'Grouped' tests follow from here
+
+ def run_all_share_mode_setup_tests(self):
+ """Tests in share mode prior to starting a share"""
+ self.click_mode(self.gui.share_mode)
+ self.file_selection_widget_has_files()
+ self.history_is_not_visible(self.gui.share_mode)
+ self.click_toggle_history(self.gui.share_mode)
+ self.history_is_visible(self.gui.share_mode)
+ self.deleting_all_files_hides_delete_button()
+ self.add_a_file_and_delete_using_its_delete_widget()
+ self.file_selection_widget_readd_files()
+
+
+ def run_all_share_mode_started_tests(self, public_mode, startup_time=2000):
+ """Tests in share mode after starting a share"""
+ self.server_working_on_start_button_pressed(self.gui.share_mode)
+ self.server_status_indicator_says_starting(self.gui.share_mode)
+ self.add_delete_buttons_hidden()
+ self.settings_button_is_hidden()
+ self.server_is_started(self.gui.share_mode, startup_time)
+ self.web_server_is_running()
+ self.have_a_slug(self.gui.share_mode, public_mode)
+ self.url_description_shown(self.gui.share_mode)
+ self.have_copy_url_button(self.gui.share_mode, public_mode)
+ self.server_status_indicator_says_started(self.gui.share_mode)
+
+
+ def run_all_share_mode_download_tests(self, public_mode, stay_open):
+ """Tests in share mode after downloading a share"""
+ self.web_page(self.gui.share_mode, 'Total size', public_mode)
+ self.download_share(public_mode)
+ self.history_widgets_present(self.gui.share_mode)
+ self.server_is_stopped(self.gui.share_mode, stay_open)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.share_mode, stay_open)
+ self.add_button_visible()
+ self.server_working_on_start_button_pressed(self.gui.share_mode)
+ self.server_is_started(self.gui.share_mode)
+ self.history_indicator(self.gui.share_mode, public_mode)
+
+
+ def run_all_share_mode_tests(self, public_mode, stay_open):
+ """End-to-end share tests"""
+ self.run_all_share_mode_setup_tests()
+ self.run_all_share_mode_started_tests(public_mode)
+ self.run_all_share_mode_download_tests(public_mode, stay_open)
+
+
+ def run_all_large_file_tests(self, public_mode, stay_open):
+ """Same as above but with a larger file"""
+ self.run_all_share_mode_setup_tests()
+ self.add_large_file()
+ self.run_all_share_mode_started_tests(public_mode, startup_time=15000)
+ self.assertTrue(self.gui.share_mode.filesize_warning.isVisible())
+ self.server_is_stopped(self.gui.share_mode, stay_open)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.share_mode, stay_open)
+
+
+ def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
+ """Same as end-to-end share tests but also test the slug is the same on multiple shared"""
+ self.run_all_share_mode_setup_tests()
+ self.run_all_share_mode_started_tests(public_mode)
+ slug = self.gui.share_mode.server_status.web.slug
+ self.run_all_share_mode_download_tests(public_mode, stay_open)
+ self.have_same_slug(slug)
+
+
+ def run_all_share_mode_timer_tests(self, public_mode):
+ """Auto-stop timer tests in share mode"""
+ self.run_all_share_mode_setup_tests()
+ self.set_timeout(self.gui.share_mode, 5)
+ self.run_all_share_mode_started_tests(public_mode)
+ self.timeout_widget_hidden(self.gui.share_mode)
+ self.server_timed_out(self.gui.share_mode, 10000)
+ self.web_server_is_stopped()
+
+
+ def run_all_share_mode_unreadable_file_tests(self):
+ '''Attempt to share an unreadable file'''
+ self.run_all_share_mode_setup_tests()
+ QtCore.QTimer.singleShot(1000, self.accept_dialog)
+ self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/nonexistent.txt')
+ self.file_selection_widget_has_files(2)
diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py
new file mode 100644
index 00000000..844a0c86
--- /dev/null
+++ b/tests/SettingsGuiBaseTest.py
@@ -0,0 +1,241 @@
+import json
+import os
+import unittest
+from PyQt5 import QtCore, QtTest
+
+from onionshare import strings
+from onionshare.common import Common
+from onionshare.settings import Settings
+from onionshare.onion import Onion
+from onionshare_gui import Application, OnionShare
+from onionshare_gui.settings_dialog import SettingsDialog
+
+
+class OnionStub(object):
+ def __init__(self, is_authenticated, supports_v3_onions):
+ self._is_authenticated = is_authenticated
+ self.supports_v3_onions = supports_v3_onions
+
+ def is_authenticated(self):
+ return self._is_authenticated
+
+
+class SettingsGuiBaseTest(object):
+ @staticmethod
+ def set_up():
+ '''Create the GUI'''
+
+ # Default settings for the settings GUI tests
+ test_settings = {
+ "no_bridges": False,
+ "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n",
+ }
+
+ # Create our test file
+ testfile = open('/tmp/test.txt', 'w')
+ testfile.write('onionshare')
+ testfile.close()
+
+ common = Common()
+ common.settings = Settings(common)
+ common.define_css()
+ strings.load_strings(common)
+
+ # Start the Onion
+ testonion = Onion(common)
+ global qtapp
+ qtapp = Application(common)
+ app = OnionShare(common, testonion, True, 0)
+
+ for key, val in common.settings.default_settings.items():
+ if key not in test_settings:
+ test_settings[key] = val
+
+ open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
+
+ gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json', True)
+ return gui
+
+ @staticmethod
+ def tear_down():
+ '''Clean up after tests'''
+ os.remove('/tmp/settings.json')
+
+ def run_settings_gui_tests(self):
+ self.gui.show()
+
+ # Window is shown
+ self.assertTrue(self.gui.isVisible())
+ self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title'))
+
+ # Check for updates button is hidden
+ self.assertFalse(self.gui.check_for_updates_button.isVisible())
+
+ # public mode is off
+ self.assertFalse(self.gui.public_mode_checkbox.isChecked())
+ # enable public mode
+ QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2))
+ self.assertTrue(self.gui.public_mode_checkbox.isChecked())
+
+ # shutdown timer is off
+ self.assertFalse(self.gui.shutdown_timeout_checkbox.isChecked())
+ # enable shutdown timer
+ QtTest.QTest.mouseClick(self.gui.shutdown_timeout_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.shutdown_timeout_checkbox.height()/2))
+ self.assertTrue(self.gui.shutdown_timeout_checkbox.isChecked())
+
+ # legacy mode checkbox and related widgets
+ if self.gui.onion.is_authenticated():
+ if self.gui.onion.supports_v3_onions:
+ # legacy mode is off
+ self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked())
+ # persistence is still available, stealth is hidden and disabled
+ self.assertTrue(self.gui.save_private_key_widget.isVisible())
+ self.assertFalse(self.gui.save_private_key_checkbox.isChecked())
+ self.assertFalse(self.gui.use_stealth_widget.isVisible())
+ self.assertFalse(self.gui.stealth_checkbox.isChecked())
+ self.assertFalse(self.gui.hidservauth_copy_button.isVisible())
+
+ # enable legacy mode
+ QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
+ self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked())
+ self.assertTrue(self.gui.save_private_key_checkbox.isVisible())
+ self.assertTrue(self.gui.use_stealth_widget.isVisible())
+
+ # enable persistent mode
+ QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
+ self.assertTrue(self.gui.save_private_key_checkbox.isChecked())
+ # enable stealth mode
+ QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
+ self.assertTrue(self.gui.stealth_checkbox.isChecked())
+ # now that stealth is enabled, we can't turn off legacy mode
+ self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
+ # disable stealth, persistence
+ QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
+ QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
+ # legacy mode checkbox is enabled again
+ self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
+ # uncheck legacy mode
+ QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
+ # legacy options hidden again
+ self.assertTrue(self.gui.save_private_key_widget.isVisible())
+ self.assertFalse(self.gui.use_stealth_widget.isVisible())
+
+ # re-enable legacy mode
+ QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
+
+ else:
+ # legacy mode setting is hidden
+ self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
+ # legacy options are showing
+ self.assertTrue(self.gui.save_private_key_widget.isVisible())
+ self.assertTrue(self.gui.use_stealth_widget.isVisible())
+
+ # enable them all again so that we can see the setting stick in settings.json
+ QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
+ QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
+ else:
+ # None of the onion settings should appear
+ self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
+ self.assertFalse(self.gui.save_private_key_widget.isVisible())
+ self.assertFalse(self.gui.save_private_key_checkbox.isChecked())
+ self.assertFalse(self.gui.use_stealth_widget.isVisible())
+ self.assertFalse(self.gui.stealth_checkbox.isChecked())
+ self.assertFalse(self.gui.hidservauth_copy_button.isVisible())
+
+ # stay open toggled off, on
+ self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked())
+ QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2))
+ self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked())
+
+ # receive mode
+ self.gui.data_dir_lineedit.setText('/tmp/OnionShareSettingsTest')
+
+
+ # bundled mode is enabled
+ self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled())
+ self.assertTrue(self.gui.connection_type_bundled_radio.isChecked())
+ # bridge options are shown
+ self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible())
+ # bridges are set to custom
+ self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked())
+ self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
+
+ # switch to obfs4
+ QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2))
+ self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked())
+
+ # custom bridges are hidden
+ self.assertFalse(self.gui.tor_bridges_use_custom_textbox_options.isVisible())
+ # other modes are unchecked but enabled
+ self.assertTrue(self.gui.connection_type_automatic_radio.isEnabled())
+ self.assertTrue(self.gui.connection_type_control_port_radio.isEnabled())
+ self.assertTrue(self.gui.connection_type_socket_file_radio.isEnabled())
+ self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
+ self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
+ self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked())
+
+ # enable automatic mode
+ QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2))
+ self.assertTrue(self.gui.connection_type_automatic_radio.isChecked())
+ # bundled is off
+ self.assertFalse(self.gui.connection_type_bundled_radio.isChecked())
+ # bridges are hidden
+ self.assertFalse(self.gui.connection_type_bridges_radio_group.isVisible())
+
+ # auth type is hidden in bundled or automatic mode
+ self.assertFalse(self.gui.authenticate_no_auth_radio.isVisible())
+ self.assertFalse(self.gui.authenticate_password_radio.isVisible())
+
+ # enable control port mode
+ QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2))
+ self.assertTrue(self.gui.connection_type_control_port_radio.isChecked())
+ # automatic is off
+ self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
+ # auth options appear
+ self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible())
+ self.assertTrue(self.gui.authenticate_password_radio.isVisible())
+
+ # enable socket mode
+ QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2))
+ self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked())
+ # control port is off
+ self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
+ # auth options are still present
+ self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible())
+ self.assertTrue(self.gui.authenticate_password_radio.isVisible())
+
+ # re-enable bundled mode
+ QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2))
+ # go back to custom bridges
+ QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2))
+ self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
+ self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible())
+ self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked())
+ self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3')
+
+ # Test that the Settings Dialog can save the settings and close itself
+ QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton)
+ self.assertFalse(self.gui.isVisible())
+
+ # Test our settings are reflected in the settings json
+ with open('/tmp/settings.json') as f:
+ data = json.load(f)
+
+ self.assertTrue(data["public_mode"])
+ self.assertTrue(data["shutdown_timeout"])
+
+ if self.gui.onion.is_authenticated():
+ if self.gui.onion.supports_v3_onions:
+ self.assertTrue(data["use_legacy_v2_onions"])
+ self.assertTrue(data["save_private_key"])
+ self.assertTrue(data["use_stealth"])
+ else:
+ self.assertFalse(data["use_legacy_v2_onions"])
+ self.assertFalse(data["save_private_key"])
+ self.assertFalse(data["use_stealth"])
+
+ self.assertEqual(data["data_dir"], "/tmp/OnionShareSettingsTest")
+ self.assertFalse(data["close_after_first_download"])
+ self.assertEqual(data["connection_type"], "bundled")
+ self.assertFalse(data["tor_bridges_use_obfs4"])
+ self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n")
diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py
new file mode 100644
index 00000000..e437ac93
--- /dev/null
+++ b/tests/TorGuiBaseTest.py
@@ -0,0 +1,173 @@
+import json
+import os
+import requests
+import socks
+
+from PyQt5 import QtCore, QtTest
+
+from onionshare import strings
+from onionshare.common import Common
+from onionshare.settings import Settings
+from onionshare.onion import Onion
+from onionshare.web import Web
+from onionshare_gui import Application, OnionShare, OnionShareGui
+from onionshare_gui.mode.share_mode import ShareMode
+from onionshare_gui.mode.receive_mode import ReceiveMode
+
+from .GuiBaseTest import GuiBaseTest
+
+class TorGuiBaseTest(GuiBaseTest):
+ @staticmethod
+ def set_up(test_settings):
+ '''Create GUI with given settings'''
+ # Create our test file
+ testfile = open('/tmp/test.txt', 'w')
+ testfile.write('onionshare')
+ testfile.close()
+
+ # Create a test dir and files
+ if not os.path.exists('/tmp/testdir'):
+ testdir = os.mkdir('/tmp/testdir')
+ testfile = open('/tmp/testdir/test.txt', 'w')
+ testfile.write('onionshare')
+ testfile.close()
+
+ common = Common()
+ common.settings = Settings(common)
+ common.define_css()
+ strings.load_strings(common)
+
+ # Get all of the settings in test_settings
+ test_settings['connection_type'] = 'automatic'
+ test_settings['data_dir'] = '/tmp/OnionShare'
+ for key, val in common.settings.default_settings.items():
+ if key not in test_settings:
+ test_settings[key] = val
+
+ # Start the Onion
+ testonion = Onion(common)
+ global qtapp
+ qtapp = Application(common)
+ app = OnionShare(common, testonion, False, 0)
+
+ web = Web(common, False, False)
+ open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
+
+ gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', False)
+ return gui
+
+ def history_indicator(self, mode, public_mode):
+ '''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
+ # Make sure history is toggled off
+ if mode.history.isVisible():
+ QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
+ self.assertFalse(mode.history.isVisible())
+
+ # Indicator should not be visible yet
+ self.assertFalse(mode.toggle_history.indicator_label.isVisible())
+
+ # Set up connecting to the onion
+ (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
+ session = requests.session()
+ session.proxies = {}
+ session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
+
+ if type(mode) == ReceiveMode:
+ # Upload a file
+ files = {'file[]': open('/tmp/test.txt', 'rb')}
+ if not public_mode:
+ path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug)
+ else:
+ path = 'http://{}/upload'.format(self.gui.app.onion_host)
+ response = session.post(path, files=files)
+ QtTest.QTest.qWait(4000)
+
+ if type(mode) == ShareMode:
+ # Download files
+ if public_mode:
+ path = "http://{}/download".format(self.gui.app.onion_host)
+ else:
+ path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug)
+ response = session.get(path)
+ QtTest.QTest.qWait(4000)
+
+ # Indicator should be visible, have a value of "1"
+ self.assertTrue(mode.toggle_history.indicator_label.isVisible())
+ self.assertEqual(mode.toggle_history.indicator_label.text(), "1")
+
+ # Toggle history back on, indicator should be hidden again
+ QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
+ self.assertFalse(mode.toggle_history.indicator_label.isVisible())
+
+ def have_an_onion_service(self):
+ '''Test that we have a valid Onion URL'''
+ self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion')
+
+ def web_page(self, mode, string, public_mode):
+ '''Test that the web page contains a string'''
+ (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
+ socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
+ s = socks.socksocket()
+ s.settimeout(60)
+ s.connect((self.gui.app.onion_host, 80))
+ if not public_mode:
+ path = '/{}'.format(mode.server_status.web.slug)
+ else:
+ path = '/'
+ http_request = 'GET {} HTTP/1.0\r\n'.format(path)
+ http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host)
+ http_request += '\r\n'
+ s.sendall(http_request.encode('utf-8'))
+ with open('/tmp/webpage', 'wb') as file_to_write:
+ while True:
+ data = s.recv(1024)
+ if not data:
+ break
+ file_to_write.write(data)
+ file_to_write.close()
+ f = open('/tmp/webpage')
+ self.assertTrue(string in f.read())
+ f.close()
+
+ def have_copy_url_button(self, mode, public_mode):
+ '''Test that the Copy URL button is shown and that the clipboard is correct'''
+ self.assertTrue(mode.server_status.copy_url_button.isVisible())
+
+ QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton)
+ clipboard = self.gui.qtapp.clipboard()
+ if public_mode:
+ self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host))
+ else:
+ self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.slug))
+
+ def cancel_the_share(self, mode):
+ '''Test that we can cancel this share before it's started up '''
+ self.server_working_on_start_button_pressed(self.gui.share_mode)
+ self.server_status_indicator_says_starting(self.gui.share_mode)
+ self.add_delete_buttons_hidden()
+ self.settings_button_is_hidden()
+ QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton)
+ QtTest.QTest.qWait(1000)
+ QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton)
+ self.assertEqual(mode.server_status.status, 0)
+ self.server_is_stopped(self.gui.share_mode, False)
+ self.web_server_is_stopped()
+
+
+ # Stealth tests
+ def copy_have_hidserv_auth_button(self, mode):
+ '''Test that the Copy HidservAuth button is shown'''
+ self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible())
+
+ def hidserv_auth_string(self):
+ '''Test the validity of the HidservAuth string'''
+ self.assertRegex(self.gui.app.auth_string, r'HidServAuth {} [a-zA-Z1-9]'.format(self.gui.app.onion_host))
+
+
+
+ # Miscellaneous tests
+ def tor_killed_statusbar_message_shown(self, mode):
+ '''Test that the status bar message shows Tor was disconnected'''
+ self.gui.app.onion.c = None
+ QtTest.QTest.qWait(1000)
+ self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost'))
diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py
new file mode 100644
index 00000000..a21dd4fc
--- /dev/null
+++ b/tests/TorGuiReceiveTest.py
@@ -0,0 +1,59 @@
+import os
+import requests
+from PyQt5 import QtTest
+from .TorGuiBaseTest import TorGuiBaseTest
+
+class TorGuiReceiveTest(TorGuiBaseTest):
+
+ def upload_file(self, public_mode, file_to_upload, expected_file):
+ '''Test that we can upload the file'''
+ (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
+ session = requests.session()
+ session.proxies = {}
+ session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
+ files = {'file[]': open(file_to_upload, 'rb')}
+ if not public_mode:
+ path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug)
+ else:
+ path = 'http://{}/upload'.format(self.gui.app.onion_host)
+ response = session.post(path, files=files)
+ QtTest.QTest.qWait(4000)
+ self.assertTrue(os.path.isfile(expected_file))
+
+
+ # 'Grouped' tests follow from here
+
+ def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown):
+ '''Run a full suite of tests in Receive mode'''
+ self.click_mode(self.gui.receive_mode)
+ self.history_is_not_visible(self.gui.receive_mode)
+ self.click_toggle_history(self.gui.receive_mode)
+ self.history_is_visible(self.gui.receive_mode)
+ self.server_working_on_start_button_pressed(self.gui.receive_mode)
+ self.server_status_indicator_says_starting(self.gui.receive_mode)
+ self.settings_button_is_hidden()
+ self.server_is_started(self.gui.receive_mode, startup_time=45000)
+ self.web_server_is_running()
+ self.have_an_onion_service()
+ self.have_a_slug(self.gui.receive_mode, public_mode)
+ self.url_description_shown(self.gui.receive_mode)
+ self.have_copy_url_button(self.gui.receive_mode, public_mode)
+ self.server_status_indicator_says_started(self.gui.receive_mode)
+ self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
+ self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt')
+ self.history_widgets_present(self.gui.receive_mode)
+ self.counter_incremented(self.gui.receive_mode, 1)
+ self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt')
+ self.counter_incremented(self.gui.receive_mode, 2)
+ self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test')
+ self.counter_incremented(self.gui.receive_mode, 3)
+ self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2')
+ self.counter_incremented(self.gui.receive_mode, 4)
+ self.history_indicator(self.gui.receive_mode, public_mode)
+ self.server_is_stopped(self.gui.receive_mode, False)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.receive_mode, False)
+ self.server_working_on_start_button_pressed(self.gui.receive_mode)
+ self.server_is_started(self.gui.receive_mode, startup_time=45000)
+ self.history_indicator(self.gui.receive_mode, public_mode)
+
diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py
new file mode 100644
index 00000000..53641dce
--- /dev/null
+++ b/tests/TorGuiShareTest.py
@@ -0,0 +1,95 @@
+import requests
+import zipfile
+from PyQt5 import QtTest
+from .TorGuiBaseTest import TorGuiBaseTest
+from .GuiShareTest import GuiShareTest
+
+class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
+ def download_share(self, public_mode):
+ '''Test downloading a share'''
+ # Set up connecting to the onion
+ (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
+ session = requests.session()
+ session.proxies = {}
+ session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
+
+ # Download files
+ if public_mode:
+ path = "http://{}/download".format(self.gui.app.onion_host)
+ else:
+ path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug)
+ response = session.get(path, stream=True)
+ QtTest.QTest.qWait(4000)
+
+ if response.status_code == 200:
+ with open('/tmp/download.zip', 'wb') as file_to_write:
+ for chunk in response.iter_content(chunk_size=128):
+ file_to_write.write(chunk)
+ file_to_write.close()
+ zip = zipfile.ZipFile('/tmp/download.zip')
+ QtTest.QTest.qWait(4000)
+ self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
+
+
+ # Persistence tests
+ def have_same_onion(self, onion):
+ '''Test that we have the same onion'''
+ self.assertEqual(self.gui.app.onion_host, onion)
+
+ # legacy v2 onion test
+ def have_v2_onion(self):
+ '''Test that the onion is a v2 style onion'''
+ self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion')
+ self.assertEqual(len(self.gui.app.onion_host), 22)
+
+ # 'Grouped' tests follow from here
+
+ def run_all_share_mode_started_tests(self, public_mode):
+ '''Tests in share mode after starting a share'''
+ self.server_working_on_start_button_pressed(self.gui.share_mode)
+ self.server_status_indicator_says_starting(self.gui.share_mode)
+ self.add_delete_buttons_hidden()
+ self.settings_button_is_hidden()
+ self.server_is_started(self.gui.share_mode, startup_time=45000)
+ self.web_server_is_running()
+ self.have_an_onion_service()
+ self.have_a_slug(self.gui.share_mode, public_mode)
+ self.url_description_shown(self.gui.share_mode)
+ self.have_copy_url_button(self.gui.share_mode, public_mode)
+ self.server_status_indicator_says_started(self.gui.share_mode)
+
+
+ def run_all_share_mode_download_tests(self, public_mode, stay_open):
+ """Tests in share mode after downloading a share"""
+ self.web_page(self.gui.share_mode, 'Total size', public_mode)
+ self.download_share(public_mode)
+ self.history_widgets_present(self.gui.share_mode)
+ self.server_is_stopped(self.gui.share_mode, stay_open)
+ self.web_server_is_stopped()
+ self.server_status_indicator_says_closed(self.gui.share_mode, stay_open)
+ self.add_button_visible()
+ self.server_working_on_start_button_pressed(self.gui.share_mode)
+ self.server_is_started(self.gui.share_mode, startup_time=45000)
+ self.history_indicator(self.gui.share_mode, public_mode)
+
+
+ def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
+ """Same as end-to-end share tests but also test the slug is the same on multiple shared"""
+ self.run_all_share_mode_setup_tests()
+ self.run_all_share_mode_started_tests(public_mode)
+ slug = self.gui.share_mode.server_status.web.slug
+ onion = self.gui.app.onion_host
+ self.run_all_share_mode_download_tests(public_mode, stay_open)
+ self.have_same_onion(onion)
+ self.have_same_slug(slug)
+
+
+ def run_all_share_mode_timer_tests(self, public_mode):
+ """Auto-stop timer tests in share mode"""
+ self.run_all_share_mode_setup_tests()
+ self.set_timeout(self.gui.share_mode, 120)
+ self.run_all_share_mode_started_tests(public_mode)
+ self.timeout_widget_hidden(self.gui.share_mode)
+ self.server_timed_out(self.gui.share_mode, 125000)
+ self.web_server_is_stopped()
+
diff --git a/test/__init__.py b/tests/__init__.py
index e69de29b..e69de29b 100644
--- a/test/__init__.py
+++ b/tests/__init__.py
diff --git a/test/conftest.py b/tests/conftest.py
index 8f10162b..7aca2b2c 100644
--- a/test/conftest.py
+++ b/tests/conftest.py
@@ -8,7 +8,32 @@ import tempfile
import pytest
-from onionshare import common
+from onionshare import common, web, settings, strings
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--rungui", action="store_true", default=False, help="run GUI tests"
+ )
+ parser.addoption(
+ "--runtor", action="store_true", default=False, help="run tor tests"
+ )
+
+
+def pytest_collection_modifyitems(config, items):
+ if not config.getoption("--runtor"):
+ # --runtor given in cli: do not skip tor tests
+ skip_tor = pytest.mark.skip(reason="need --runtor option to run")
+ for item in items:
+ if "tor" in item.keywords:
+ item.add_marker(skip_tor)
+
+ if not config.getoption('--rungui'):
+ # --rungui given in cli: do not skip GUI tests
+ skip_gui = pytest.mark.skip(reason="need --rungui option to run")
+ for item in items:
+ if "gui" in item.keywords:
+ item.add_marker(skip_gui)
+
@pytest.fixture
def temp_dir_1024():
@@ -64,8 +89,9 @@ def temp_file_1024_delete():
# pytest > 2.9 only needs @pytest.fixture
@pytest.yield_fixture(scope='session')
def custom_zw():
- zw = common.ZipWriter(
- zip_filename=common.random_string(4, 6),
+ zw = web.share_mode.ZipWriter(
+ common.Common(),
+ zip_filename=common.Common.random_string(4, 6),
processed_size_callback=lambda _: 'custom_callback'
)
yield zw
@@ -76,7 +102,7 @@ def custom_zw():
# pytest > 2.9 only needs @pytest.fixture
@pytest.yield_fixture(scope='session')
def default_zw():
- zw = common.ZipWriter()
+ zw = web.share_mode.ZipWriter(common.Common())
yield zw
zw.close()
tmp_dir = os.path.dirname(zw.zip_filename)
@@ -119,16 +145,6 @@ def platform_windows(monkeypatch):
@pytest.fixture
-def set_debug_false(monkeypatch):
- monkeypatch.setattr('onionshare.common.debug', False)
-
-
-@pytest.fixture
-def set_debug_true(monkeypatch):
- monkeypatch.setattr('onionshare.common.debug', True)
-
-
-@pytest.fixture
def sys_argv_sys_prefix(monkeypatch):
monkeypatch.setattr('sys.argv', [sys.prefix])
@@ -157,3 +173,14 @@ def time_time_100(monkeypatch):
@pytest.fixture
def time_strftime(monkeypatch):
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
+
+@pytest.fixture
+def common_obj():
+ return common.Common()
+
+@pytest.fixture
+def settings_obj(sys_onionshare_dev_mode, platform_linux):
+ _common = common.Common()
+ _common.version = 'DUMMY_VERSION_1.2.3'
+ strings.load_strings(_common)
+ return settings.Settings(_common)
diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py
new file mode 100644
index 00000000..ebb524c2
--- /dev/null
+++ b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": False,
+ "public_mode": True
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(True, True)
+ self.hit_404(True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py
new file mode 100644
index 00000000..8ed0777a
--- /dev/null
+++ b/tests/local_onionshare_404_triggers_ratelimit_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class Local404RateLimitTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": False
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, True)
+ self.hit_404(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py
new file mode 100644
index 00000000..e57167c8
--- /dev/null
+++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+from PyQt5 import QtCore, QtTest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": False
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, True)
+ # Prepare our auto-accept of prompt
+ QtCore.QTimer.singleShot(5000, self.accept_dialog)
+ # Try to close the app
+ self.gui.close()
+ # Server should still be running (we've been prompted first)
+ self.server_is_started(self.gui.share_mode, 0)
+ self.web_server_is_running()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py
new file mode 100644
index 00000000..bfb9499a
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_sender_closed_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceiveModeSenderClosedTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_tests(False, True)
+ self.run_receive_mode_sender_closed_tests(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py
new file mode 100644
index 00000000..0acaa4a9
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_timer_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": False,
+ "shutdown_timeout": True,
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_timer_tests(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py
new file mode 100644
index 00000000..a1dcd679
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_unwritable_dir_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py
new file mode 100644
index 00000000..529e0c87
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": True,
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_unwritable_dir_tests(True, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py
new file mode 100644
index 00000000..f8bd38bd
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": True,
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_tests(True, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py
new file mode 100644
index 00000000..362e3b85
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_upload_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiReceiveTest import GuiReceiveTest
+
+class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = GuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_settings_dialog_legacy_tor_test.py b/tests/local_onionshare_settings_dialog_legacy_tor_test.py
new file mode 100644
index 00000000..f32023fe
--- /dev/null
+++ b/tests/local_onionshare_settings_dialog_legacy_tor_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from onionshare import strings
+from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
+
+
+class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
+ @classmethod
+ def setUpClass(cls):
+ cls.gui = SettingsGuiBaseTest.set_up()
+
+ @classmethod
+ def tearDownClass(cls):
+ SettingsGuiBaseTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui_legacy_tor(self):
+ self.gui.onion = OnionStub(True, False)
+ self.gui.reload_settings()
+ self.run_settings_gui_tests()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py
new file mode 100644
index 00000000..b34cbff3
--- /dev/null
+++ b/tests/local_onionshare_settings_dialog_no_tor_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from onionshare import strings
+from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
+
+
+class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
+ @classmethod
+ def setUpClass(cls):
+ cls.gui = SettingsGuiBaseTest.set_up()
+
+ @classmethod
+ def tearDownClass(cls):
+ SettingsGuiBaseTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui_no_tor(self):
+ self.gui.onion = OnionStub(False, False)
+ self.gui.reload_settings()
+ self.run_settings_gui_tests()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_settings_dialog_v3_tor_test.py b/tests/local_onionshare_settings_dialog_v3_tor_test.py
new file mode 100644
index 00000000..1dd17c0f
--- /dev/null
+++ b/tests/local_onionshare_settings_dialog_v3_tor_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from onionshare import strings
+from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
+
+
+class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
+ @classmethod
+ def setUpClass(cls):
+ cls.gui = SettingsGuiBaseTest.set_up()
+
+ @classmethod
+ def tearDownClass(cls):
+ SettingsGuiBaseTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui_v3_tor(self):
+ self.gui.onion = OnionStub(True, True)
+ self.gui.reload_settings()
+ self.run_settings_gui_tests()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py
new file mode 100644
index 00000000..f9a9dc4c
--- /dev/null
+++ b/tests/local_onionshare_share_mode_download_public_mode_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": True,
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(True, False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py
new file mode 100644
index 00000000..65304924
--- /dev/null
+++ b/tests/local_onionshare_share_mode_download_stay_open_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": False,
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py
new file mode 100644
index 00000000..ea16683e
--- /dev/null
+++ b/tests/local_onionshare_share_mode_download_test.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py
new file mode 100644
index 00000000..7d0595a2
--- /dev/null
+++ b/tests/local_onionshare_share_mode_large_download_test.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_large_file_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py
new file mode 100644
index 00000000..7254f3ff
--- /dev/null
+++ b/tests/local_onionshare_share_mode_slug_persistent_test.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": False,
+ "slug": "",
+ "save_private_key": True,
+ "close_after_first_download": False,
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_persistent_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py
new file mode 100644
index 00000000..e30ce4ec
--- /dev/null
+++ b/tests/local_onionshare_share_mode_timer_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": False,
+ "shutdown_timeout": True,
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_timer_tests(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_timer_too_short_test.py b/tests/local_onionshare_share_mode_timer_too_short_test.py
new file mode 100644
index 00000000..8d22048d
--- /dev/null
+++ b/tests/local_onionshare_share_mode_timer_too_short_test.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+from PyQt5 import QtCore, QtTest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": False,
+ "shutdown_timeout": True,
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_setup_tests()
+ # Set a low timeout
+ self.set_timeout(self.gui.share_mode, 2)
+ QtTest.QTest.qWait(3000)
+ QtCore.QTimer.singleShot(4000, self.accept_dialog)
+ QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
+ self.assertEqual(self.gui.share_mode.server_status.status, 0)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_share_mode_unreadable_file_test.py b/tests/local_onionshare_share_mode_unreadable_file_test.py
new file mode 100644
index 00000000..c74d7a0a
--- /dev/null
+++ b/tests/local_onionshare_share_mode_unreadable_file_test.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiShareTest import GuiShareTest
+
+class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = GuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_unreadable_file_tests()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py
new file mode 100644
index 00000000..4b7673bb
--- /dev/null
+++ b/tests/onionshare_790_cancel_on_second_share_test.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+# Tests #790 regression
+class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": True
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, False)
+ self.cancel_the_share(self.gui.share_mode)
+ self.server_is_stopped(self.gui.share_mode, False)
+ self.web_server_is_stopped()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py
new file mode 100644
index 00000000..3c733f0a
--- /dev/null
+++ b/tests/onionshare_receive_mode_upload_public_mode_test.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiReceiveTest import TorGuiReceiveTest
+
+class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": True,
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = TorGuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_tests(True, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py
new file mode 100644
index 00000000..493cacc7
--- /dev/null
+++ b/tests/onionshare_receive_mode_upload_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiReceiveTest import TorGuiReceiveTest
+
+class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "receive_allow_receiver_shutdown": True
+ }
+ cls.gui = TorGuiReceiveTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiReceiveTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_receive_mode_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py
new file mode 100644
index 00000000..ed28ddd7
--- /dev/null
+++ b/tests/onionshare_share_mode_cancel_share_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_setup_tests()
+ self.cancel_the_share(self.gui.share_mode)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py
new file mode 100644
index 00000000..eb9adb1e
--- /dev/null
+++ b/tests/onionshare_share_mode_download_public_mode_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": True,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(True, False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py
new file mode 100644
index 00000000..93a41f1f
--- /dev/null
+++ b/tests/onionshare_share_mode_download_stay_open_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "close_after_first_download": False,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py
new file mode 100644
index 00000000..ac3dee76
--- /dev/null
+++ b/tests/onionshare_share_mode_download_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py
new file mode 100644
index 00000000..13588a68
--- /dev/null
+++ b/tests/onionshare_share_mode_persistent_test.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "use_legacy_v2_onions": True,
+ "public_mode": False,
+ "slug": "",
+ "save_private_key": True,
+ "close_after_first_download": False,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_persistent_tests(False, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py
new file mode 100644
index 00000000..bb9114a6
--- /dev/null
+++ b/tests/onionshare_share_mode_stealth_test.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "use_legacy_v2_onions": True,
+ "use_stealth": True,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_setup_tests()
+ self.run_all_share_mode_started_tests(False)
+ self.hidserv_auth_string()
+ self.copy_have_hidserv_auth_button(self.gui.share_mode)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py
new file mode 100644
index 00000000..7f636a71
--- /dev/null
+++ b/tests/onionshare_share_mode_timer_test.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "public_mode": False,
+ "shutdown_timeout": True,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_timer_tests(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py
new file mode 100644
index 00000000..cf48df3d
--- /dev/null
+++ b/tests/onionshare_share_mode_tor_connection_killed_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_setup_tests()
+ self.run_all_share_mode_started_tests(False)
+ self.tor_killed_statusbar_message_shown(self.gui.share_mode)
+ self.server_is_stopped(self.gui.share_mode, False)
+ self.web_server_is_stopped()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/onionshare_share_mode_v2_onion_test.py b/tests/onionshare_share_mode_v2_onion_test.py
new file mode 100644
index 00000000..18f5f058
--- /dev/null
+++ b/tests/onionshare_share_mode_v2_onion_test.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .TorGuiShareTest import TorGuiShareTest
+
+class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "use_legacy_v2_onions": True,
+ }
+ cls.gui = TorGuiShareTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ TorGuiShareTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.tor
+ def test_gui(self):
+ self.run_all_common_setup_tests()
+ self.run_all_share_mode_tests(False, False)
+ self.have_v2_onion()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_helpers.py b/tests/test_helpers.py
index b0e94a5c..321afbb7 100644
--- a/test/test_helpers.py
+++ b/tests/test_helpers.py
@@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/test/test_onionshare.py b/tests/test_onionshare.py
index b9e50ba1..7592a777 100644
--- a/test/test_onionshare.py
+++ b/tests/test_onionshare.py
@@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ import os
import pytest
from onionshare import OnionShare
+from onionshare.common import Common
class MyOnion:
@@ -37,7 +38,8 @@ class MyOnion:
@pytest.fixture
def onionshare_obj():
- return OnionShare(MyOnion())
+ common = Common()
+ return OnionShare(common, MyOnion())
class TestOnionShare:
@@ -47,7 +49,6 @@ class TestOnionShare:
assert onionshare_obj.stealth is None
assert onionshare_obj.cleanup_filenames == []
assert onionshare_obj.local_only is False
- assert onionshare_obj.stay_open is False
def test_set_stealth_true(self, onionshare_obj):
onionshare_obj.set_stealth(True)
diff --git a/test/test_onionshare_common.py b/tests/test_onionshare_common.py
index d3c78710..d70f2c0e 100644
--- a/test/test_onionshare_common.py
+++ b/tests/test_onionshare_common.py
@@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -29,17 +29,16 @@ import zipfile
import pytest
-from onionshare import common
-
-DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
LOG_MSG_REGEX = re.compile(r"""
^\[Jun\ 06\ 2013\ 11:05:00\]
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
-RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
+# TODO: Improve the Common tests to test it all as a single class
+
+
class TestBuildSlug:
@pytest.mark.parametrize('test_input,expected', (
# VALID, two lowercase words, separated by a hyphen
@@ -79,17 +78,17 @@ class TestBuildSlug:
assert bool(SLUG_REGEX.match(test_input)) == expected
- def test_build_slug_unique(self, sys_onionshare_dev_mode):
- assert common.build_slug() != common.build_slug()
+ def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode):
+ assert common_obj.build_slug() != common_obj.build_slug()
class TestDirSize:
- def test_temp_dir_size(self, temp_dir_1024_delete):
+ def test_temp_dir_size(self, common_obj, temp_dir_1024_delete):
""" dir_size() should return the total size (in bytes) of all files
in a particular directory.
"""
- assert common.dir_size(temp_dir_1024_delete) == 1024
+ assert common_obj.dir_size(temp_dir_1024_delete) == 1024
class TestEstimatedTimeRemaining:
@@ -103,16 +102,16 @@ class TestEstimatedTimeRemaining:
((971, 1009, 83), '1s')
))
def test_estimated_time_remaining(
- self, test_input, expected, time_time_100):
- assert common.estimated_time_remaining(*test_input) == expected
+ self, common_obj, test_input, expected, time_time_100):
+ assert common_obj.estimated_time_remaining(*test_input) == expected
@pytest.mark.parametrize('test_input', (
(10, 20, 100), # if `time_elapsed == 0`
(0, 37, 99) # if `download_rate == 0`
))
- def test_raises_zero_division_error(self, test_input, time_time_100):
+ def test_raises_zero_division_error(self, common_obj, test_input, time_time_100):
with pytest.raises(ZeroDivisionError):
- common.estimated_time_remaining(*test_input)
+ common_obj.estimated_time_remaining(*test_input)
class TestFormatSeconds:
@@ -131,16 +130,16 @@ class TestFormatSeconds:
(129674, '1d12h1m14s'),
(56404.12, '15h40m4s')
))
- def test_format_seconds(self, test_input, expected):
- assert common.format_seconds(test_input) == expected
+ def test_format_seconds(self, common_obj, test_input, expected):
+ assert common_obj.format_seconds(test_input) == expected
# TODO: test negative numbers?
@pytest.mark.parametrize('test_input', (
'string', lambda: None, [], {}, set()
))
- def test_invalid_input_types(self, test_input):
+ def test_invalid_input_types(self, common_obj, test_input):
with pytest.raises(TypeError):
- common.format_seconds(test_input)
+ common_obj.format_seconds(test_input)
class TestGetAvailablePort:
@@ -148,29 +147,29 @@ class TestGetAvailablePort:
(random.randint(1024, 1500),
random.randint(1800, 2048)) for _ in range(50)
))
- def test_returns_an_open_port(self, port_min, port_max):
+ def test_returns_an_open_port(self, common_obj, port_min, port_max):
""" get_available_port() should return an open port within the range """
- port = common.get_available_port(port_min, port_max)
+ port = common_obj.get_available_port(port_min, port_max)
assert port_min <= port <= port_max
with socket.socket() as tmpsock:
tmpsock.bind(('127.0.0.1', port))
class TestGetPlatform:
- def test_darwin(self, platform_darwin):
- assert common.get_platform() == 'Darwin'
+ def test_darwin(self, platform_darwin, common_obj):
+ assert common_obj.platform == 'Darwin'
- def test_linux(self, platform_linux):
- assert common.get_platform() == 'Linux'
+ def test_linux(self, platform_linux, common_obj):
+ assert common_obj.platform == 'Linux'
- def test_windows(self, platform_windows):
- assert common.get_platform() == 'Windows'
+ def test_windows(self, platform_windows, common_obj):
+ assert common_obj.platform == 'Windows'
# TODO: double-check these tests
class TestGetResourcePath:
- def test_onionshare_dev_mode(self, sys_onionshare_dev_mode):
+ def test_onionshare_dev_mode(self, common_obj, sys_onionshare_dev_mode):
prefix = os.path.join(
os.path.dirname(
os.path.dirname(
@@ -178,29 +177,29 @@ class TestGetResourcePath:
inspect.getfile(
inspect.currentframe())))), 'share')
assert (
- common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
+ common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
os.path.join(prefix, 'test_filename'))
- def test_linux(self, platform_linux, sys_argv_sys_prefix):
+ def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix):
prefix = os.path.join(sys.prefix, 'share/onionshare')
assert (
- common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
+ common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
os.path.join(prefix, 'test_filename'))
- def test_frozen_darwin(self, platform_darwin, sys_frozen, sys_meipass):
+ def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass):
prefix = os.path.join(sys._MEIPASS, 'share')
assert (
- common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
+ common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
os.path.join(prefix, 'test_filename'))
class TestGetTorPaths:
# @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ?
- def test_get_tor_paths_darwin(self, platform_darwin, sys_frozen, sys_meipass):
+ def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass):
base_path = os.path.dirname(
os.path.dirname(
os.path.dirname(
- common.get_resource_path(''))))
+ common_obj.get_resource_path(''))))
tor_path = os.path.join(
base_path, 'Resources', 'Tor', 'tor')
tor_geo_ip_file_path = os.path.join(
@@ -209,20 +208,20 @@ class TestGetTorPaths:
base_path, 'Resources', 'Tor', 'geoip6')
obfs4proxy_file_path = os.path.join(
base_path, 'Resources', 'Tor', 'obfs4proxy')
- assert (common.get_tor_paths() ==
+ assert (common_obj.get_tor_paths() ==
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
- def test_get_tor_paths_linux(self, platform_linux):
- assert (common.get_tor_paths() ==
+ def test_get_tor_paths_linux(self, platform_linux, common_obj):
+ assert (common_obj.get_tor_paths() ==
('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy'))
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
- def test_get_tor_paths_windows(self, platform_windows, sys_frozen):
+ def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen):
base_path = os.path.join(
os.path.dirname(
os.path.dirname(
- common.get_resource_path(''))), 'tor')
+ common_obj.get_resource_path(''))), 'tor')
tor_path = os.path.join(
os.path.join(base_path, 'Tor'), 'tor.exe')
obfs4proxy_file_path = os.path.join(
@@ -233,18 +232,10 @@ class TestGetTorPaths:
tor_geo_ipv6_file_path = os.path.join(
os.path.join(
os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
- assert (common.get_tor_paths() ==
+ assert (common_obj.get_tor_paths() ==
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
-class TestGetVersion:
- def test_get_version(self, sys_onionshare_dev_mode):
- with open(common.get_resource_path('version.txt')) as f:
- version = f.read().strip()
-
- assert version == common.get_version()
-
-
class TestHumanReadableFilesize:
@pytest.mark.parametrize('test_input,expected', (
(1024 ** 0, '1.0 B'),
@@ -257,8 +248,8 @@ class TestHumanReadableFilesize:
(1024 ** 7, '1.0 ZiB'),
(1024 ** 8, '1.0 YiB')
))
- def test_human_readable_filesize(self, test_input, expected):
- assert common.human_readable_filesize(test_input) == expected
+ def test_human_readable_filesize(self, common_obj, test_input, expected):
+ assert common_obj.human_readable_filesize(test_input) == expected
class TestLog:
@@ -273,82 +264,18 @@ class TestLog:
def test_log_msg_regex(self, test_input):
assert bool(LOG_MSG_REGEX.match(test_input))
- def test_output(self, set_debug_true, time_strftime):
+ def test_output(self, common_obj, time_strftime):
def dummy_func():
pass
+ common_obj.debug = True
+
# From: https://stackoverflow.com/questions/1218933
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
- common.log('TestModule', dummy_func)
- common.log('TestModule', dummy_func, 'TEST_MSG')
+ common_obj.log('TestModule', dummy_func)
+ common_obj.log('TestModule', dummy_func, 'TEST_MSG')
output = buf.getvalue()
line_one, line_two, _ = output.split('\n')
assert LOG_MSG_REGEX.match(line_one)
assert LOG_MSG_REGEX.match(line_two)
-
-
-class TestSetDebug:
- def test_debug_true(self, set_debug_false):
- common.set_debug(True)
- assert common.debug is True
-
- def test_debug_false(self, set_debug_true):
- common.set_debug(False)
- assert common.debug is False
-
-
-class TestZipWriterDefault:
- @pytest.mark.parametrize('test_input', (
- 'onionshare_{}.zip'.format(''.join(
- random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6)
- )) for _ in range(50)
- ))
- def test_default_zw_filename_regex(self, test_input):
- assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
-
- def test_zw_filename(self, default_zw):
- zw_filename = os.path.basename(default_zw.zip_filename)
- assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename))
-
- def test_zipfile_filename_matches_zipwriter_filename(self, default_zw):
- assert default_zw.z.filename == default_zw.zip_filename
-
- def test_zipfile_allow_zip64(self, default_zw):
- assert default_zw.z._allowZip64 is True
-
- def test_zipfile_mode(self, default_zw):
- assert default_zw.z.mode == 'w'
-
- def test_callback(self, default_zw):
- assert default_zw.processed_size_callback(None) is None
-
- def test_add_file(self, default_zw, temp_file_1024_delete):
- default_zw.add_file(temp_file_1024_delete)
- zipfile_info = default_zw.z.getinfo(
- os.path.basename(temp_file_1024_delete))
-
- assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
- assert zipfile_info.file_size == 1024
-
- def test_add_directory(self, temp_dir_1024_delete, default_zw):
- previous_size = default_zw._size # size before adding directory
- default_zw.add_dir(temp_dir_1024_delete)
- assert default_zw._size == previous_size + 1024
-
-
-class TestZipWriterCustom:
- @pytest.mark.parametrize('test_input', (
- common.random_string(
- random.randint(2, 50),
- random.choice((None, random.randint(2, 50)))
- ) for _ in range(50)
- ))
- def test_random_string_regex(self, test_input):
- assert bool(RANDOM_STR_REGEX.match(test_input))
-
- def test_custom_filename(self, custom_zw):
- assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
-
- def test_custom_callback(self, custom_zw):
- assert custom_zw.processed_size_callback(None) == 'custom_callback'
diff --git a/test/test_onionshare_settings.py b/tests/test_onionshare_settings.py
index 212aceb8..f4be2930 100644
--- a/test/test_onionshare_settings.py
+++ b/tests/test_onionshare_settings.py
@@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -27,23 +27,20 @@ from onionshare import common, settings, strings
@pytest.fixture
-def custom_version(monkeypatch):
- monkeypatch.setattr(common, 'get_version', lambda: 'DUMMY_VERSION_1.2.3')
-
-
-@pytest.fixture
def os_path_expanduser(monkeypatch):
monkeypatch.setattr('os.path.expanduser', lambda path: path)
@pytest.fixture
-def settings_obj(custom_version, sys_onionshare_dev_mode, platform_linux):
- return settings.Settings()
+def settings_obj(sys_onionshare_dev_mode, platform_linux):
+ _common = common.Common()
+ _common.version = 'DUMMY_VERSION_1.2.3'
+ return settings.Settings(_common)
class TestSettings:
def test_init(self, settings_obj):
- assert settings_obj._settings == settings_obj.default_settings == {
+ expected_settings = {
'version': 'DUMMY_VERSION_1.2.3',
'connection_type': 'bundled',
'control_port_address': '127.0.0.1',
@@ -54,21 +51,27 @@ class TestSettings:
'auth_type': 'no_auth',
'auth_password': '',
'close_after_first_download': True,
- 'systray_notifications': True,
'shutdown_timeout': False,
'use_stealth': False,
'use_autoupdate': True,
'autoupdate_timestamp': None,
'no_bridges': True,
'tor_bridges_use_obfs4': False,
- 'tor_bridges_use_meek_lite_amazon': False,
'tor_bridges_use_meek_lite_azure': False,
'tor_bridges_use_custom_bridges': '',
+ 'use_legacy_v2_onions': False,
'save_private_key': False,
'private_key': '',
'slug': '',
- 'hidservauth_string': ''
+ 'hidservauth_string': '',
+ 'data_dir': os.path.expanduser('~/OnionShare'),
+ 'public_mode': False
}
+ for key in settings_obj._settings:
+ # Skip locale, it will not always default to the same thing
+ if key != 'locale':
+ assert settings_obj._settings[key] == settings_obj.default_settings[key]
+ assert settings_obj._settings[key] == expected_settings[key]
def test_fill_in_defaults(self, settings_obj):
del settings_obj._settings['version']
@@ -121,14 +124,12 @@ class TestSettings:
assert settings_obj.get('auth_type') == 'no_auth'
assert settings_obj.get('auth_password') == ''
assert settings_obj.get('close_after_first_download') is True
- assert settings_obj.get('systray_notifications') is True
assert settings_obj.get('use_stealth') is False
assert settings_obj.get('use_autoupdate') is True
assert settings_obj.get('autoupdate_timestamp') is None
assert settings_obj.get('autoupdate_timestamp') is None
assert settings_obj.get('no_bridges') is True
assert settings_obj.get('tor_bridges_use_obfs4') is False
- assert settings_obj.get('tor_bridges_use_meek_lite_amazon') is False
assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False
assert settings_obj.get('tor_bridges_use_custom_bridges') == ''
@@ -153,31 +154,28 @@ class TestSettings:
def test_filename_darwin(
self,
- custom_version,
monkeypatch,
os_path_expanduser,
platform_darwin):
- obj = settings.Settings()
+ obj = settings.Settings(common.Common())
assert (obj.filename ==
'~/Library/Application Support/OnionShare/onionshare.json')
def test_filename_linux(
self,
- custom_version,
monkeypatch,
os_path_expanduser,
platform_linux):
- obj = settings.Settings()
+ obj = settings.Settings(common.Common())
assert obj.filename == '~/.config/onionshare/onionshare.json'
def test_filename_windows(
self,
- custom_version,
monkeypatch,
platform_windows):
monkeypatch.setenv('APPDATA', 'C:')
- obj = settings.Settings()
- assert obj.filename == 'C:\\OnionShare\\onionshare.json'
+ obj = settings.Settings(common.Common())
+ assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json'
def test_set_custom_bridge(self, settings_obj):
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')
diff --git a/test/test_onionshare_strings.py b/tests/test_onionshare_strings.py
index 0e7a32ab..3ac22a11 100644
--- a/test/test_onionshare_strings.py
+++ b/tests/test_onionshare_strings.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,8 +22,8 @@ import types
import pytest
-from onionshare import common, strings
-
+from onionshare import strings
+from onionshare.settings import Settings
# # Stub get_resource_path so it finds the correct path while running tests
# def get_resource_path(filename):
@@ -32,40 +32,31 @@ from onionshare import common, strings
# return path
# common.get_resource_path = get_resource_path
-
-def test_starts_with_empty_strings():
- """ Creates an empty strings dict by default """
- assert strings.strings == {}
-
-
def test_underscore_is_function():
assert callable(strings._) and isinstance(strings._, types.FunctionType)
class TestLoadStrings:
def test_load_strings_defaults_to_english(
- self, locale_en, sys_onionshare_dev_mode):
+ self, common_obj, locale_en, sys_onionshare_dev_mode):
""" load_strings() loads English by default """
- strings.load_strings(common)
- assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
+ common_obj.settings = Settings(common_obj)
+ strings.load_strings(common_obj)
+ assert strings._('preparing_files') == "Compressing files."
def test_load_strings_loads_other_languages(
- self, locale_fr, sys_onionshare_dev_mode):
+ self, common_obj, locale_fr, sys_onionshare_dev_mode):
""" load_strings() loads other languages in different locales """
- strings.load_strings(common, "fr")
- assert strings._('wait_for_hs') == "En attente du HS:"
-
- def test_load_partial_strings(
- self, locale_ru, sys_onionshare_dev_mode):
- strings.load_strings(common)
- assert strings._("give_this_url") == (
- "Отправьте эту ссылку тому человеку, "
- "которому вы хотите передать файл:")
- assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
+ common_obj.settings = Settings(common_obj)
+ common_obj.settings.set('locale', 'fr')
+ strings.load_strings(common_obj)
+ assert strings._('preparing_files') == "Compression des fichiers."
def test_load_invalid_locale(
- self, locale_invalid, sys_onionshare_dev_mode):
+ self, common_obj, locale_invalid, sys_onionshare_dev_mode):
""" load_strings() raises a KeyError for an invalid locale """
with pytest.raises(KeyError):
- strings.load_strings(common, 'XX')
+ common_obj.settings = Settings(common_obj)
+ common_obj.settings.set('locale', 'XX')
+ strings.load_strings(common_obj)
diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py
new file mode 100644
index 00000000..3f9540ae
--- /dev/null
+++ b/tests/test_onionshare_web.py
@@ -0,0 +1,226 @@
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import contextlib
+import inspect
+import io
+import os
+import random
+import re
+import socket
+import sys
+import zipfile
+import tempfile
+
+import pytest
+
+from onionshare.common import Common
+from onionshare import strings
+from onionshare.web import Web
+from onionshare.settings import Settings
+
+DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
+RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
+
+
+def web_obj(common_obj, mode, num_files=0):
+ """ Creates a Web object, in either share mode or receive mode, ready for testing """
+ common_obj.load_settings()
+ strings.load_strings(common_obj)
+ web = Web(common_obj, False, mode)
+ web.generate_slug()
+ web.stay_open = True
+ web.running = True
+
+ web.app.testing = True
+
+ # Share mode
+ if mode == 'share':
+ # Add files
+ files = []
+ for i in range(num_files):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
+ tmp_file.write(b'*' * 1024)
+ files.append(tmp_file.name)
+ web.share_mode.set_file_info(files)
+ # Receive mode
+ else:
+ pass
+
+ return web
+
+
+class TestWeb:
+ def test_share_mode(self, common_obj):
+ web = web_obj(common_obj, 'share', 3)
+ assert web.mode is 'share'
+ with web.app.test_client() as c:
+ # Load 404 pages
+ res = c.get('/')
+ res.get_data()
+ assert res.status_code == 404
+
+ res = c.get('/invalidslug'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 404
+
+ # Load download page
+ res = c.get('/{}'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 200
+
+ # Download
+ res = c.get('/{}/download'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 200
+ assert res.mimetype == 'application/zip'
+
+ def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024):
+ web = web_obj(common_obj, 'share', 3)
+ web.stay_open = False
+
+ assert web.running == True
+
+ with web.app.test_client() as c:
+ # Download the first time
+ res = c.get('/{}/download'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 200
+ assert res.mimetype == 'application/zip'
+
+ assert web.running == False
+
+ def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024):
+ web = web_obj(common_obj, 'share', 3)
+ web.stay_open = True
+
+ assert web.running == True
+
+ with web.app.test_client() as c:
+ # Download the first time
+ res = c.get('/{}/download'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 200
+ assert res.mimetype == 'application/zip'
+ assert web.running == True
+
+ def test_receive_mode(self, common_obj):
+ web = web_obj(common_obj, 'receive')
+ assert web.mode is 'receive'
+
+ with web.app.test_client() as c:
+ # Load 404 pages
+ res = c.get('/')
+ res.get_data()
+ assert res.status_code == 404
+
+ res = c.get('/invalidslug'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 404
+
+ # Load upload page
+ res = c.get('/{}'.format(web.slug))
+ res.get_data()
+ assert res.status_code == 200
+
+ def test_public_mode_on(self, common_obj):
+ web = web_obj(common_obj, 'receive')
+ common_obj.settings.set('public_mode', True)
+
+ with web.app.test_client() as c:
+ # Upload page should be accessible from /
+ res = c.get('/')
+ data1 = res.get_data()
+ assert res.status_code == 200
+
+ # /[slug] should be a 404
+ res = c.get('/{}'.format(web.slug))
+ data2 = res.get_data()
+ assert res.status_code == 404
+
+ def test_public_mode_off(self, common_obj):
+ web = web_obj(common_obj, 'receive')
+ common_obj.settings.set('public_mode', False)
+
+ with web.app.test_client() as c:
+ # / should be a 404
+ res = c.get('/')
+ data1 = res.get_data()
+ assert res.status_code == 404
+
+ # Upload page should be accessible from /[slug]
+ res = c.get('/{}'.format(web.slug))
+ data2 = res.get_data()
+ assert res.status_code == 200
+
+
+class TestZipWriterDefault:
+ @pytest.mark.parametrize('test_input', (
+ 'onionshare_{}.zip'.format(''.join(
+ random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6)
+ )) for _ in range(50)
+ ))
+ def test_default_zw_filename_regex(self, test_input):
+ assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
+
+ def test_zw_filename(self, default_zw):
+ zw_filename = os.path.basename(default_zw.zip_filename)
+ assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename))
+
+ def test_zipfile_filename_matches_zipwriter_filename(self, default_zw):
+ assert default_zw.z.filename == default_zw.zip_filename
+
+ def test_zipfile_allow_zip64(self, default_zw):
+ assert default_zw.z._allowZip64 is True
+
+ def test_zipfile_mode(self, default_zw):
+ assert default_zw.z.mode == 'w'
+
+ def test_callback(self, default_zw):
+ assert default_zw.processed_size_callback(None) is None
+
+ def test_add_file(self, default_zw, temp_file_1024_delete):
+ default_zw.add_file(temp_file_1024_delete)
+ zipfile_info = default_zw.z.getinfo(
+ os.path.basename(temp_file_1024_delete))
+
+ assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
+ assert zipfile_info.file_size == 1024
+
+ def test_add_directory(self, temp_dir_1024_delete, default_zw):
+ previous_size = default_zw._size # size before adding directory
+ default_zw.add_dir(temp_dir_1024_delete)
+ assert default_zw._size == previous_size + 1024
+
+
+class TestZipWriterCustom:
+ @pytest.mark.parametrize('test_input', (
+ Common.random_string(
+ random.randint(2, 50),
+ random.choice((None, random.randint(2, 50)))
+ ) for _ in range(50)
+ ))
+ def test_random_string_regex(self, test_input):
+ assert bool(RANDOM_STR_REGEX.match(test_input))
+
+ def test_custom_filename(self, custom_zw):
+ assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
+
+ def test_custom_callback(self, custom_zw):
+ assert custom_zw.processed_size_callback(None) == 'custom_callback'