macOS packaging on Github Actions, update translations (#768)

This commit is contained in:
Manu 2021-01-19 15:17:10 +08:00 committed by GitHub
parent a47af86d64
commit 9b88c15a22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 3126 additions and 109 deletions

View File

@ -12,3 +12,6 @@ trim_trailing_whitespace = true
[Makefile]
indent_style = tab
[.github/**.yml]
indent_size = 2

50
.github/workflows/build-macos.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: Build macOS release
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to use for building macOS release'
required: true
default: 'master'
borg_version:
description: 'Borg version to package'
required: true
default: '1.1.15'
jobs:
build:
runs-on: macos-10.15
steps:
- name: Check out selected branch
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install system dependencies
run: |
brew upgrade openssl readline xz
- name: Install build dependencies
run: |
brew install --cask sparkle
pip install -r dev.txt
working-directory: requirements.d
- name: Install Vorta
run: |
pip install .
- name: Package with PyInstaller
run: |
pyinstaller --clean --noconfirm package/vorta.spec
cp -R /usr/local/Caskroom/sparkle/*/Sparkle.framework dist/Vorta.app/Contents/Frameworks/
curl -LJO https://github.com/borgbackup/borg/releases/download/${{ github.event.inputs.borg_version }}/borg-macosx64.tgz
tar xvf borg-macosx64.tgz -C dist/Vorta.app/Contents/Resources/
cd dist && zip -rq --symlinks Vorta.zip Vorta.app
- name: Upload build
uses: actions/upload-artifact@v2
with:
name: Vorta macOS
path: dist/Vorta.zip
retention-days: 10

1
.gitignore vendored
View File

@ -15,7 +15,6 @@ vorta.egg-info
.vagrant
*.log
htmlcov
*.qm
src/vorta/i18n/ts/vorta.en.ts
flatpak/app/
flatpak/.flatpak-builder/

View File

@ -8,7 +8,7 @@ VERSION := $(shell python -c "from src.vorta._version import __version__; print(
clean:
rm -rf dist/*
dist/Vorta.app: translations-to-qm clean
dist/Vorta.app:
pyinstaller --clean --noconfirm package/vorta.spec
cp -R bin/darwin/Sparkle.framework dist/Vorta.app/Contents/Frameworks/
cp -R ${BORG_SRC}/dist/borg-dir dist/Vorta.app/Contents/Resources/
@ -24,7 +24,9 @@ borg: ## Build Borg single-dir release for bundling in macOS
--entitlements package/entitlements.plist --timestamp --deep --options runtime {} \;
dist/Vorta.dmg: dist/Vorta.app ## Create notarized macOS DMG for distribution.
sh package/macos-package-app.sh
# Workaround for dots in filenames. See https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing-Qt
python3 package/fix_app_qt_folder_names_for_codesign.py dist/Vorta.app
cd dist && sh ../package/macos-package-app.sh
github-release: dist/Vorta.dmg ## Add new Github release and attach macOS DMG
cp dist/Vorta.dmg dist/vorta-${VERSION}.dmg

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import os
import shutil
import sys
from pathlib import Path
from typing import Generator, List, Optional
from macholib.MachO import MachO
def create_symlink(folder: Path) -> None:
"""Create the appropriate symlink in the MacOS folder
pointing to the Resources folder.
"""
sibbling = Path(str(folder).replace("MacOS", ""))
# PyQt5/Qt/qml/QtQml/Models.2
root = str(sibbling).partition("Contents")[2].lstrip("/")
# ../../../../
backward = "../" * (root.count("/") + 1)
# ../../../../Resources/PyQt5/Qt/qml/QtQml/Models.2
good_path = f"{backward}Resources/{root}"
folder.symlink_to(good_path)
def fix_dll(dll: Path) -> None:
"""Fix the DLL lookup paths to use relative ones for Qt dependencies.
Inspiration: PyInstaller/depend/dylib.py:mac_set_relative_dylib_deps()
Currently one header is pointing to (we are in the Resources folder):
@loader_path/../../../../QtCore (it is referencing to the old MacOS folder)
It will be converted to:
@loader_path/../../../../../../MacOS/QtCore
"""
def match_func(pth: str) -> Optional[str]:
"""Callback function for MachO.rewriteLoadCommands() that is
called on every lookup path setted in the DLL headers.
By returning None for system libraries, it changes nothing.
Else we return a relative path pointing to the good file
in the MacOS folder.
"""
basename = os.path.basename(pth)
if not basename.startswith("Qt"):
return None
return f"@loader_path{good_path}/{basename}"
# Resources/PyQt5/Qt/qml/QtQuick/Controls.2/Fusion
root = str(dll.parent).partition("Contents")[2][1:]
# /../../../../../../..
backward = "/.." * (root.count("/") + 1)
# /../../../../../../../MacOS
good_path = f"{backward}/MacOS"
# Rewrite Mach headers with corrected @loader_path
dll = MachO(dll)
dll.rewriteLoadCommands(match_func)
with open(dll.filename, "rb+") as f:
for header in dll.headers:
f.seek(0)
dll.write(f)
f.seek(0, 2)
f.flush()
def find_problematic_folders(folder: Path) -> Generator[Path, None, None]:
"""Recursively yields problematic folders (containing a dot in their name)."""
for path in folder.iterdir():
if not path.is_dir() or path.is_symlink():
# Skip simlinks as they are allowed (even with a dot)
continue
if "." in path.name:
yield path
else:
yield from find_problematic_folders(path)
def move_contents_to_resources(folder: Path) -> Generator[Path, None, None]:
"""Recursively move any non symlink file from a problematic folder
to the sibbling one in Resources.
"""
for path in folder.iterdir():
if path.is_symlink():
continue
if path.name == "qml":
yield from move_contents_to_resources(path)
else:
sibbling = Path(str(path).replace("MacOS", "Resources"))
sibbling.parent.mkdir(parents=True, exist_ok=True)
shutil.move(path, sibbling)
yield sibbling
def main(args: List[str]) -> int:
"""
Fix the application to allow codesign (NXDRIVE-1301).
Take one or more .app as arguments: "Nuxeo Drive.app".
To overall process will:
- move problematic folders from MacOS to Resources
- fix the DLLs lookup paths
- create the appropriate symbolic link
"""
for app in args:
name = os.path.basename(app)
print(f">>> [{name}] Fixing Qt folder names")
path = Path(app) / "Contents" / "MacOS"
for folder in find_problematic_folders(path):
for file in move_contents_to_resources(folder):
try:
fix_dll(file)
except (ValueError, IsADirectoryError):
continue
shutil.rmtree(folder)
create_symlink(folder)
print(f" !! Fixed {folder}")
print(f">>> [{name}] Application fixed.")
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

View File

@ -1,35 +1,50 @@
#!/usr/bin/env bash
# Inspired by https://github.com/metabrainz/picard/blob/master/scripts/package/macos-notarize-app.sh
set -e
set -eux
CERTIFICATE_NAME="Developer ID Application: Manuel Riel (CNMSCAXT48)"
APP_BUNDLE_ID="com.borgbase.client.macos"
APP_BUNDLE="Vorta"
APPLE_ID_USER="manu@snapdragon.cc"
APPLE_ID_PASSWORD="@keychain:Notarization"
# CERTIFICATE_NAME="Developer ID Application: Joe Doe (XXXXXX)"
# APPLE_ID_USER="name@example.com"
# APPLE_ID_PASSWORD="@keychain:Notarization"
cd dist
# codesign --deep is only 1 level deep. It misses Sparkle embedded app AutoUpdate
# Sign app bundle, Sparkle and Borg
codesign --verbose --force --sign "$CERTIFICATE_NAME" --timestamp --deep --options runtime \
$APP_BUNDLE.app/Contents/Frameworks/Sparkle.framework/Resources/Autoupdate.app
find $APP_BUNDLE.app/Contents/Resources/borg-dir \
-type f \( -name \*.so -or -name \*.dylib -or -name borg.exe -or -name Python \) \
-exec codesign --verbose --force --timestamp --deep --sign "${CERTIFICATE_NAME}" \
--entitlements ../package/entitlements.plist --options runtime {} \;
codesign --verify --force --verbose --deep \
--options runtime --timestamp \
--entitlements ../package/entitlements.plist \
--sign "$CERTIFICATE_NAME" $APP_BUNDLE.app
# ditto -c -k --rsrc --keepParent "$APP_BUNDLE.app" "${APP_BUNDLE}.zip"
rm -rf $APP_BUNDLE.dmg
appdmg ../package/appdmg.json $APP_BUNDLE.dmg
# Create DMG
rm -rf $APP_BUNDLE.dmg
create-dmg \
--volname "Vorta Installer" \
--window-size 410 300 \
--icon-size 100 \
--icon "Vorta.app" 70 150 \
--hide-extension "Vorta.app" \
--app-drop-link 240 150 \
"Vorta.dmg" \
"Vorta.app"
# Notarize DMG
RESULT=$(xcrun altool --notarize-app --type osx \
--primary-bundle-id $APP_BUNDLE_ID \
--username $APPLE_ID_USER --password $APPLE_ID_PASSWORD \
--file "$APP_BUNDLE.dmg" --output-format xml)
REQUEST_UUID=$(echo "$RESULT" | xpath \
REQUEST_UUID=$(echo "$RESULT" | xpath -q -e \
"//key[normalize-space(text()) = 'RequestUUID']/following-sibling::string[1]/text()" 2> /dev/null)
# Poll for notarization status
@ -41,7 +56,7 @@ do
--username "$APPLE_ID_USER" \
--password "$APPLE_ID_PASSWORD" \
--output-format xml)
STATUS=$(echo "$RESULT" | xpath "//key[normalize-space(text()) = 'Status']/following-sibling::string[1]/text()" 2> /dev/null)
STATUS=$(echo "$RESULT" | xpath -q -e "//key[normalize-space(text()) = 'Status']/following-sibling::string[1]/text()" 2> /dev/null)
if [ "$STATUS" = "success" ]; then
echo "Notarization of $APP_BUNDLE succeeded!"
@ -59,4 +74,4 @@ done
# Staple the notary ticket
xcrun stapler staple $APP_BUNDLE.dmg
xcrun stapler staple $APP_BUNDLE.app
xcrun stapler validate $APP_BUNDLE.dmg
xcrun stapler validate $APP_BUNDLE.dmg

View File

@ -1,7 +1,9 @@
# Install required non-Python dev packages using Homebrew on macOS:
# Run `brew bundle` while in this folder
brew 'create-dmg'
brew 'qt'
brew 'hub'
brew 'xmlstarlet'
cask 'qt-creator'
cask 'sparkle'

View File

@ -1,9 +1,10 @@
transifex-client
pytest
pytest-qt
pytest-mock
pytest-faulthandler
pyinstaller
tox
flake8
pyinstaller
pylint
pytest
pytest-faulthandler
pytest-mock
pytest-qt
tox
transifex-client
wheel

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
<クd<>箆!ソ`。スン

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -956,38 +956,38 @@
<message>
<location filename="../../assets/UI/repotab.ui" line="149"/>
<source>Copy repo URL to clipboard</source>
<translation type="unfinished"/>
<translation>Adresse der Installationsquelle in die Zwischenablage kopieren</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="382"/>
<source>Don&apos;t run backup over metered networks</source>
<translation type="unfinished"/>
<translation>Kein Backup über kostenpflichtige Netzwerke durchführen</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="60"/>
<source>Add File(s)</source>
<translation type="unfinished"/>
<translation>Datei(en) hinzufügen</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="67"/>
<source>One folder or file per line</source>
<translation type="unfinished"/>
<translation>Ein Verzeichnis oder eine Datei pro Zeile</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="70"/>
<source>Paste Folders/Files</source>
<translation type="unfinished"/>
<translation>Verzeichnisse/Dateien einfügen</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../views/main_window.py" line="94"/>
<location filename="../../views/main_window.py" line="95"/>
<source>Backup in progress.</source>
<translation>Backup läuft</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="180"/>
<location filename="../../views/main_window.py" line="181"/>
<source>Task cancelled</source>
<translation>Aufgabe abgebrochen</translation>
</message>
@ -1062,12 +1062,12 @@
<translation>Neues Profil hinzufügen</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="147"/>
<location filename="../../views/main_window.py" line="148"/>
<source>Are you sure you want to delete profile &apos;{}&apos;?</source>
<translation>Soll das gewählte Archiv gelöscht werden?</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="148"/>
<location filename="../../views/main_window.py" line="149"/>
<source>Confirm deletion</source>
<translation>Löschen bestätigen</translation>
</message>
@ -1075,12 +1075,12 @@
<context>
<name>MainWindow QMessagebox</name>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Quit</source>
<translation>Beenden</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Should Vorta continue to run in the background?</source>
<translation>Soll Vorta im Hintergrund weiter ausgeführt werden?</translation>
</message>
@ -1221,7 +1221,7 @@
<message>
<location filename="../../views/source_tab.py" line="49"/>
<source>Choose file(s) to back up</source>
<translation type="unfinished"/>
<translation>Datei(en) für die Sicherung auswählen</translation>
</message>
</context>
<context>
@ -1373,7 +1373,7 @@
<message>
<location filename="../../borg/create.py" line="97"/>
<source>Not running backup over metered connection.</source>
<translation type="unfinished"/>
<translation>Sicherung über kostenpflichtige Verbindung wird nicht durchgeführt.</translation>
</message>
</context>
<context>

View File

@ -14,7 +14,7 @@
<message>
<location filename="../../views/profile_add_edit_dialog.py" line="55"/>
<source>A profile with this name already exists.</source>
<translation>Profiili samalla nimellä on olemassa.</translation>
<translation>Samanniminen profiili on jo olemassa.</translation>
</message>
<message>
<location filename="../../views/profile_add_edit_dialog.py" line="20"/>
@ -57,7 +57,7 @@
<message>
<location filename="../../views/repo_add_dialog.py" line="122"/>
<source>This repo has already been added.</source>
<translation type="unfinished"/>
<translation>Tämä tietovarasto on jo lisätty.</translation>
</message>
<message>
<location filename="../../views/repo_add_dialog.py" line="128"/>
@ -158,7 +158,7 @@
<message>
<location filename="../../views/archive_tab.py" line="160"/>
<source>Error in archive name template.</source>
<translation type="unfinished"/>
<translation>Virhe arkistonimen kaavassa.</translation>
</message>
<message>
<location filename="../../views/archive_tab.py" line="202"/>
@ -168,7 +168,7 @@
<message>
<location filename="../../views/archive_tab.py" line="219"/>
<source>Refreshed archives.</source>
<translation>Päivitettiin arkistot.</translation>
<translation>Arkistot päivitetty.</translation>
</message>
<message>
<location filename="../../views/archive_tab.py" line="262"/>
@ -188,7 +188,7 @@
<message>
<location filename="../../views/archive_tab.py" line="303"/>
<source>Un-mounted successfully.</source>
<translation>Irrotettiin liitos onnistuneesti.</translation>
<translation>Liitos irrotettu onnistuneesti.</translation>
</message>
<message>
<location filename="../../views/archive_tab.py" line="341"/>
@ -385,7 +385,7 @@
<message>
<location filename="../../assets/UI/extractdialog.ui" line="14"/>
<source>Dialog</source>
<translation type="unfinished"/>
<translation>Valintaikkuna</translation>
</message>
<message>
<location filename="../../assets/UI/extractdialog.ui" line="25"/>
@ -395,12 +395,12 @@
<message>
<location filename="../../assets/UI/extractdialog.ui" line="38"/>
<source>nyx2.local-2018-11-16T09:49:58 from November 16, 2018</source>
<translation type="unfinished"/>
<translation>nyx2.local-2018-11-16T09:49:58 marraskuun 16., 2018</translation>
</message>
<message>
<location filename="../../assets/UI/extractdialog.ui" line="63"/>
<source>Note: If you select a top-level folder and deselect its children, they will still be restored.</source>
<translation type="unfinished"/>
<translation>Huomio: Jos valitset ylätason hakemiston, sen alihakemistot palautetaan vaikka niitä ei olisi valittu.</translation>
</message>
<message>
<location filename="../../assets/UI/extractdialog.ui" line="88"/>
@ -420,7 +420,7 @@
<message>
<location filename="../../assets/UI/profileadd.ui" line="56"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Backup profiles allow for granular backups from different sources to different destinations. You could e.g. back up essential documents to a remote repository via Wifi, while doing a full backup onto a local storage device.&lt;/p&gt;&lt;p&gt;Repositories and SSH keys are shared between profiles. Source folders, active destination repo, allowed networks, pruning, validation and scheduling are per-profile.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Varmuuskopioinnin profiilit mahdollistavat jaotellut varmuuskopiot eri lähteille eri kohteisiin. Voit esimerkiksi varmuuskopioida vain tärkeät dokumentit etäpalvelimelle Wifin kautta ja luoda täyden varmuuskopion paikalliselle tallennuslaitteelle.&lt;/p&gt;&lt;p&gt;Tietovarastot ja SSH-avaimet jaetaan profiilien kesken. Lähdekansiot, aktiiviset kohdetietovarastot sekä verkko-, karsimis-, tarkistus- ja ajastusasetukset ovat profiilikohtaisia.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/profileadd.ui" line="84"/>
@ -500,7 +500,7 @@
<message>
<location filename="../../assets/UI/diffdialog.ui" line="113"/>
<source>Diff</source>
<translation type="unfinished"/>
<translation>Eroavaisuudet</translation>
</message>
<message>
<location filename="../../assets/UI/diffresult.ui" line="25"/>
@ -510,7 +510,7 @@
<message>
<location filename="../../assets/UI/diffresult.ui" line="38"/>
<source>nyx2.local-2018-11-16T09:49:58 </source>
<translation type="unfinished"/>
<translation>nyx2.local-2018-11-16T09:49:58 </translation>
</message>
<message>
<location filename="../../assets/UI/diffresult.ui" line="45"/>
@ -520,7 +520,7 @@
<message>
<location filename="../../assets/UI/diffresult.ui" line="58"/>
<source>nyx2.local-2018-10-16T09:49:58 </source>
<translation type="unfinished"/>
<translation>nyx2.local-2018-10-16T09:49:58 </translation>
</message>
<message>
<location filename="../../assets/UI/diffresult.ui" line="101"/>
@ -541,7 +541,7 @@
<message>
<location filename="../../assets/UI/sourcetab.ui" line="14"/>
<source>Form</source>
<translation type="unfinished"/>
<translation>Lomake</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="44"/>
@ -621,37 +621,37 @@
<message>
<location filename="../../assets/UI/archivetab.ui" line="255"/>
<source> hourly, </source>
<translation type="unfinished"/>
<translation>tunnittaista,</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="272"/>
<source> daily, </source>
<translation type="unfinished"/>
<translation>päivittäistä,</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="286"/>
<source> weekly, </source>
<translation type="unfinished"/>
<translation>viikottaista,</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="300"/>
<source> monthly and</source>
<translation type="unfinished"/>
<translation>kuukausittaista ja</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="314"/>
<source> annual archives</source>
<translation type="unfinished"/>
<translation>vuosittaista arkistoa</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="341"/>
<source>No matter what, keep all archives of the last:</source>
<translation type="unfinished"/>
<translation>Säilytä aina arkistot, jotka on luotu tämän ajan sisällä:</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="351"/>
<source>24H, 1d, 52w, 12m, 1y</source>
<translation type="unfinished"/>
<translation>24t, 1pv, 52vk, 12kk, 1v</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="391"/>
@ -666,17 +666,17 @@
<message>
<location filename="../../assets/UI/archivetab.ui" line="401"/>
<source>{hostname}-{profile_slug}-</source>
<translation type="unfinished"/>
<translation>{hostname}-{profile_slug}-</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="435"/>
<source>{hostname}-{profile_slug}-{now:%Y-%m-%dT%H:%M:%S}</source>
<translation type="unfinished"/>
<translation>{hostname}-{profile_slug}-{now:%Y-%m-%dT%H:%M:%S}</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="442"/>
<source>Prune Prefix:</source>
<translation type="unfinished"/>
<translation>Karsittavien etuliite:</translation>
</message>
<message>
<location filename="../../assets/UI/misctab.ui" line="71"/>
@ -741,7 +741,7 @@
<message>
<location filename="../../assets/UI/scheduletab.ui" line="47"/>
<source>Schedule</source>
<translation>Aikataulu</translation>
<translation>Ajastus</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="73"/>
@ -756,7 +756,7 @@
<message>
<location filename="../../assets/UI/scheduletab.ui" line="113"/>
<source>hours at</source>
<translation>tunnin välein</translation>
<translation>tunnin välein,</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="136"/>
@ -866,7 +866,7 @@
<message>
<location filename="../../assets/UI/sourcetab.ui" line="20"/>
<source>Source Folders and Files to Back Up:</source>
<translation>Lähdekansiot ja -tiedostot varmuuskopioitavaksi:</translation>
<translation>Varmuuskopioitavat kansiot ja tiedostot:</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="53"/>
@ -881,12 +881,12 @@
<message>
<location filename="../../assets/UI/sourcetab.ui" line="103"/>
<source>Exclude If Present (exclude folders with these files):</source>
<translation>Ohita jos läsnä (ohita kansiot, joiden sisällä olevien tiedostojen nimissä):</translation>
<translation>Ohita hakemistot, jotka sisältävät tiedoston, jonka nimi sisältää:</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="152"/>
<source>Diff</source>
<translation type="unfinished"/>
<translation>Erot</translation>
</message>
<message>
<location filename="../../assets/UI/archivetab.ui" line="192"/>
@ -896,7 +896,7 @@
<message>
<location filename="../../assets/UI/archivetab.ui" line="217"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pruning removes older archives. You can choose the number of hourly, daily, etc. archives to preserve. Usually you will keep more newer and fewer old archives. Read &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/prune.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#FF4500;&quot;&gt;more&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Karsiminen poistaa vanhoja arkistoja. Voit valita, kuinka monta tunnittaista, päivittäistä jne. arkistoa säilytetään. Yleensä säilytetään useampia uudemmista arkistoista ja harvempia vanhemmista. Lue &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/prune.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#FF4500;&quot;&gt;lisää&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/misctab.ui" line="64"/>
@ -931,17 +931,17 @@
<message>
<location filename="../../assets/UI/repotab.ui" line="99"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Remote or local backup repository. For simple and secure backup hosting, try &lt;a href=&quot;https://www.borgbase.com/?utm_source=vorta&amp;amp;utm_medium=app&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tietovarasto etäpalvelimella tai paikallisesti. Yksinkertaiseen ja turvalliseen varmuuskopiointiin testaa &lt;a href=&quot;https://www.borgbase.com/?utm_source=vorta&amp;amp;utm_medium=app&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;BorgBasea&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/repotab.ui" line="297"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Compression used for new data. Can be changed and doesn&apos;t affect deduplication. Read &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-compression&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;more&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pakkausmetodi uudelle datalle. Tätä voi vaihtaa eikä se vaikuta deduplikaatioon. Lue &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-compression&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;lisää&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="93"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exclude Patterns (&lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;more&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kaavat tiedon poisjättämiselle (&lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;lisää&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="119"/>
@ -956,45 +956,45 @@
<message>
<location filename="../../assets/UI/repotab.ui" line="149"/>
<source>Copy repo URL to clipboard</source>
<translation type="unfinished"/>
<translation>Kopioi tietovaraston URL leikepöydälle</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="382"/>
<source>Don&apos;t run backup over metered networks</source>
<translation type="unfinished"/>
<translation>Ä varmuuskopioi laskutettavissa verkoissa</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="60"/>
<source>Add File(s)</source>
<translation type="unfinished"/>
<translation>Lisää tiedosto(ja)</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="67"/>
<source>One folder or file per line</source>
<translation type="unfinished"/>
<translation>Yksi hakemisto tai tiedosto per rivi</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="70"/>
<source>Paste Folders/Files</source>
<translation type="unfinished"/>
<translation>Liitä kansioita/tiedostoja</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../views/main_window.py" line="94"/>
<location filename="../../views/main_window.py" line="95"/>
<source>Backup in progress.</source>
<translation>Varmuuskopiointi käynnissä.</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="180"/>
<location filename="../../views/main_window.py" line="181"/>
<source>Task cancelled</source>
<translation>Tehtävä peruttu</translation>
</message>
<message>
<location filename="../../assets/UI/mainwindow.ui" line="20"/>
<source>MainWindow</source>
<translation type="unfinished"/>
<translation>MainWindow</translation>
</message>
<message>
<location filename="../../assets/UI/mainwindow.ui" line="51"/>
@ -1024,7 +1024,7 @@
<message>
<location filename="../../assets/UI/mainwindow.ui" line="145"/>
<source>Schedule</source>
<translation>Aikataulu</translation>
<translation>Ajastus</translation>
</message>
<message>
<location filename="../../assets/UI/mainwindow.ui" line="150"/>
@ -1062,12 +1062,12 @@
<translation>Lisää profiili</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="147"/>
<location filename="../../views/main_window.py" line="148"/>
<source>Are you sure you want to delete profile &apos;{}&apos;?</source>
<translation>Haluatko varmasti poistaa profiilin &apos;{}&apos;?</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="148"/>
<location filename="../../views/main_window.py" line="149"/>
<source>Confirm deletion</source>
<translation>Vahvista poistaminen</translation>
</message>
@ -1075,12 +1075,12 @@
<context>
<name>MainWindow QMessagebox</name>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Quit</source>
<translation>Lopeta</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Should Vorta continue to run in the background?</source>
<translation>Tulisiko Vortan jatkaa käynnissä taustalla?</translation>
</message>
@ -1221,7 +1221,7 @@
<message>
<location filename="../../views/source_tab.py" line="49"/>
<source>Choose file(s) to back up</source>
<translation type="unfinished"/>
<translation>Valitse varmuuskopioitava(t) tiedosto(t)</translation>
</message>
</context>
<context>
@ -1229,7 +1229,7 @@
<message>
<location filename="../../tray_menu.py" line="39"/>
<source>Vorta for Borg Backup</source>
<translation>Vorta Borg-varmuuskopiointiin</translation>
<translation>Vorta Borg-varmuuskopiointi</translation>
</message>
<message>
<location filename="../../tray_menu.py" line="48"/>
@ -1300,12 +1300,12 @@
<message>
<location filename="../../scheduler.py" line="98"/>
<source>Starting background backup for %s.</source>
<translation type="unfinished"/>
<translation>Aloitetaan profiilin %s varmuuskopiointia taustalla.</translation>
</message>
<message>
<location filename="../../scheduler.py" line="109"/>
<source>Backup successful for %s.</source>
<translation type="unfinished"/>
<translation>Profiilin %s varmuuskopiointi onnistui.</translation>
</message>
<message>
<location filename="../../scheduler.py" line="115"/>
@ -1373,7 +1373,7 @@
<message>
<location filename="../../borg/create.py" line="97"/>
<source>Not running backup over metered connection.</source>
<translation type="unfinished"/>
<translation>Varmuuskopiointia ei suoriteta laskutettavalla yhteydellä.</translation>
</message>
</context>
<context>
@ -1381,7 +1381,7 @@
<message>
<location filename="../../models.py" line="200"/>
<source>Display notifications when background tasks fail</source>
<translation>Näytä ilmoitukset kun taustatehtävät epäonnistuvat</translation>
<translation>Näytä ilmoitukset jos taustatehtävät epäonnistuvat</translation>
</message>
<message>
<location filename="../../models.py" line="205"/>

View File

@ -280,12 +280,12 @@
<message>
<location filename="../../borg/diff.py" line="8"/>
<source>Requesting differences between archives...</source>
<translation type="unfinished"/>
<translation>Récupère des différences entre les archives</translation>
</message>
<message>
<location filename="../../borg/diff.py" line="12"/>
<source>Obtained differences between archives.</source>
<translation type="unfinished"/>
<translation>Différences entre les archives récupérées.</translation>
</message>
</context>
<context>
@ -356,7 +356,7 @@
<message>
<location filename="../../borg/prune.py" line="9"/>
<source>Pruning old archives...</source>
<translation>Élagage des vieilles archives...</translation>
<translation>Élagage des anciennes archives...</translation>
</message>
<message>
<location filename="../../borg/prune.py" line="14"/>
@ -420,7 +420,7 @@
<message>
<location filename="../../assets/UI/profileadd.ui" line="56"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Backup profiles allow for granular backups from different sources to different destinations. You could e.g. back up essential documents to a remote repository via Wifi, while doing a full backup onto a local storage device.&lt;/p&gt;&lt;p&gt;Repositories and SSH keys are shared between profiles. Source folders, active destination repo, allowed networks, pruning, validation and scheduling are per-profile.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Les profils de sauvegarde permettent des sauvegardes granulaires depuis différentes sources vers des destinations différentes. Vous pouvez, par exemple, sauvegarder des documents essentiels vers un dépôt distant via Wifi et réaliser une sauvegarde complète sur un appareil de stockage local. &lt;/p&gt;&lt;p&gt;Les dépôts et clés SSH sont partagées entre les profils. Les dossiers sources, dépôts de destination actifs, réseaux autorisé, paramètres d&apos;élagage et de validation ainsi que les horaires de sauvegarde sont spécifiques au profil. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Les profils de sauvegarde permettent des sauvegardes granulaires depuis différentes sources vers des destinations différentes. Vous pouvez, par exemple, sauvegarder des documents essentiels vers un dépôt distant via Wifi et réaliser une sauvegarde complète sur un appareil de stockage local. &lt;/p&gt;&lt;p&gt;Les dépôts et clés SSH sont partagées entre les profils. Les dossiers sources, dépôts de destination actifs, réseaux autorisés, paramètres d&apos;élagage et de validation ainsi que les horaires de sauvegarde sont spécifiques au profil. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/profileadd.ui" line="84"/>
@ -505,7 +505,7 @@
<message>
<location filename="../../assets/UI/diffresult.ui" line="25"/>
<source>Difference between</source>
<translation type="unfinished"/>
<translation>Différence entre</translation>
</message>
<message>
<location filename="../../assets/UI/diffresult.ui" line="38"/>
@ -781,7 +781,7 @@
<message>
<location filename="../../assets/UI/scheduletab.ui" line="243"/>
<source>Prune old Archives after each backup</source>
<translation>Élaguer les vieilles archives après chaque sauvegarde</translation>
<translation>Élaguer les anciennes archives après chaque sauvegarde</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="283"/>
@ -801,7 +801,7 @@
<message>
<location filename="../../assets/UI/scheduletab.ui" line="363"/>
<source>Networks</source>
<translation>Réseaux :</translation>
<translation>Réseaux</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="375"/>
@ -896,7 +896,7 @@
<message>
<location filename="../../assets/UI/archivetab.ui" line="217"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pruning removes older archives. You can choose the number of hourly, daily, etc. archives to preserve. Usually you will keep more newer and fewer old archives. Read &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/prune.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#FF4500;&quot;&gt;more&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L&apos;élagage efface les archives les plus anciennes. Vous pouvez choisir le nombre d&apos;archives qui sont conservées par heure, jour, etc. En règle générale, on conserve plus d&apos;archives récentes et moins d&apos;anciennes. &lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/prune.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#FF4500;&quot;&gt;En savoir plus&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/misctab.ui" line="64"/>
@ -942,7 +942,7 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<message>
<location filename="../../assets/UI/sourcetab.ui" line="93"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exclude Patterns (&lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;more&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exclure les paternes (&lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;plus&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Exclure les motifs (&lt;a href=&quot;https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0984e3;&quot;&gt;plus&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="119"/>
@ -957,38 +957,38 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<message>
<location filename="../../assets/UI/repotab.ui" line="149"/>
<source>Copy repo URL to clipboard</source>
<translation type="unfinished"/>
<translation>Copier l&apos;URL du dépôt dans le presse-papier</translation>
</message>
<message>
<location filename="../../assets/UI/scheduletab.ui" line="382"/>
<source>Don&apos;t run backup over metered networks</source>
<translation type="unfinished"/>
<translation>Ne pas lancer de sauvegarde via une connexion limitée.</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="60"/>
<source>Add File(s)</source>
<translation type="unfinished"/>
<translation>Ajouter des fichiers</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="67"/>
<source>One folder or file per line</source>
<translation type="unfinished"/>
<translation>Un dossier ou fichier par ligne</translation>
</message>
<message>
<location filename="../../assets/UI/sourcetab.ui" line="70"/>
<source>Paste Folders/Files</source>
<translation type="unfinished"/>
<translation>Coller les Dossiers/Fichiers</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../views/main_window.py" line="94"/>
<location filename="../../views/main_window.py" line="95"/>
<source>Backup in progress.</source>
<translation>Sauvegarde en cours.</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="180"/>
<location filename="../../views/main_window.py" line="181"/>
<source>Task cancelled</source>
<translation>Tâche annulée</translation>
</message>
@ -1063,12 +1063,12 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<translation>Ajouter un profil</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="147"/>
<location filename="../../views/main_window.py" line="148"/>
<source>Are you sure you want to delete profile &apos;{}&apos;?</source>
<translation>Êtes vous sûr·e de vouloir supprimer le profil &apos;{}&apos;?</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="148"/>
<location filename="../../views/main_window.py" line="149"/>
<source>Confirm deletion</source>
<translation>Confirmer la suppression</translation>
</message>
@ -1076,12 +1076,12 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<context>
<name>MainWindow QMessagebox</name>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Quit</source>
<translation>Quitter</translation>
</message>
<message>
<location filename="../../views/main_window.py" line="192"/>
<location filename="../../views/main_window.py" line="193"/>
<source>Should Vorta continue to run in the background?</source>
<translation>Vorta doit-il continuer à fonctionner en arrière-plan ?</translation>
</message>
@ -1222,7 +1222,7 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<message>
<location filename="../../views/source_tab.py" line="49"/>
<source>Choose file(s) to back up</source>
<translation type="unfinished"/>
<translation>Choisissez les fichier à sauvegarder</translation>
</message>
</context>
<context>
@ -1374,7 +1374,7 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<message>
<location filename="../../borg/create.py" line="97"/>
<source>Not running backup over metered connection.</source>
<translation type="unfinished"/>
<translation>Connexion limitée : la sauvegarde ne va pas être lancée.</translation>
</message>
</context>
<context>
@ -1382,7 +1382,7 @@ BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translatio
<message>
<location filename="../../models.py" line="200"/>
<source>Display notifications when background tasks fail</source>
<translation>Afficher les notifications lorsqu&apos;une tâche en arrière-plan échoue</translation>
<translation>Afficher une notifications lorsqu&apos;une tâche en arrière-plan échoue.</translation>
</message>
<message>
<location filename="../../models.py" line="205"/>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff