Quellcode durchsuchen

Merge branch 'master' into karthikkasturi-master

El RIDO vor 9 Monaten
Ursprung
Commit
bef55b3bb1
4 geänderte Dateien mit 284 neuen und 139 gelöschten Zeilen
  1. 13 13
      i18n/es.json
  2. 128 120
      i18n/pl.json
  3. 137 0
      js/test/emailTemplateTest.js
  4. 6 6
      tst/I18nTest.php

+ 13 - 13
i18n/es.json

@@ -214,22 +214,22 @@
     "Save document": "Guardar documento",
     "Your IP is not authorized to create documents.": "Tu IP no está autorizada para crear contenido.",
     "Trying to shorten a URL that isn't pointing at our instance.": "Intentando acortar una URL que no apunta a nuestra instancia.",
-    "Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.": "Error llamando a YOURLS. Probablemente un problema de configuración, como error o falta \"apiurl\" o \"signature\".",
-    "Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.": "Error al analizar la respuesta de YOURLS.",
-    "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.": "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.",
+    "Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.": "Proxy error: Proxy URL is empty. Probablemente un problema de configuración, como error o falta \"apiurl\" o \"signature\".",
+    "Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.": "Proxy error: Error al analizar la respuesta de proxy. Probablemente un problema de configuración, como error o falta \"apiurl\" o \"signature\".",
+    "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.": "Proxy error: Error llamando a proxy. Probablemente un problema de configuración, como error o falta \"apiurl\" o \"signature\".",
     "This secret message can only be displayed once. Would you like to see it now?": "Este mensaje secreto sólo se puede mostrar una vez. ¿Quieres verlo ahora?",
     "Yes, see it": "Sí, verlo",
     "Dark Mode": "Modo nocturno",
-    "Error compressing document, due to missing WebAssembly support.": "Error compressing document, due to missing WebAssembly support.",
-    "Error decompressing document, your browser does not support WebAssembly. Please use another browser to view this document.": "Error decompressing document, your browser does not support WebAssembly. Please use another browser to view this document.",
-    "Start over": "Start over",
-    "Document copied to clipboard": "Document copied to clipboard",
-    "To copy document press on the copy button or use the clipboard shortcut <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>": "To copy document press on the copy button or use the clipboard shortcut <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>",
-    "Copy link": "Copy link",
-    "Link copied to clipboard": "Link copied to clipboard",
-    "Document text": "Document text",
-    "Tabulator key serves as character (Hit <kbd>Ctrl</kbd>+<kbd>m</kbd> or <kbd>Esc</kbd> to toggle)": "Tabulator key serves as character (Hit <kbd>Ctrl</kbd>+<kbd>m</kbd> or <kbd>Esc</kbd> to toggle)",
+    "Error compressing document, due to missing WebAssembly support.": "Error al comprimir el documento debido a la falta de compatibilidad con WebAssembly.",
+    "Error decompressing document, your browser does not support WebAssembly. Please use another browser to view this document.": "Error al descomprimir el documento. Su navegador no es compatible con WebAssembly. Utilice otro navegador para ver este documento.",
+    "Start over": "Empezar de nuevo",
+    "Document copied to clipboard": "Documento copiado al portapapeles",
+    "To copy document press on the copy button or use the clipboard shortcut <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>": "Para copiar el documento presione el botón copiar o use el atajo del portapapeles <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>",
+    "Copy link": "Copiar enlace",
+    "Link copied to clipboard": "Enlace copiado al portapapeles",
+    "Document text": "Texto del documento",
+    "Tabulator key serves as character (Hit <kbd>Ctrl</kbd>+<kbd>m</kbd> or <kbd>Esc</kbd> to toggle)": "La tecla de tabulación funciona como carácter (presione <kbd>Ctrl</kbd>+<kbd>m</kbd> o <kbd>Esc</kbd> para alternar)",
     "Show password": "Show password",
     "Hide password": "Hide password",
-    "Theme": "Theme"
+    "Theme": "Tema"
 }

+ 128 - 120
i18n/pl.json

@@ -1,49 +1,52 @@
 {
     "PrivateBin": "PrivateBin",
-    "%s is a minimalist, open source online pastebin where the server has zero knowledge of stored data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s jest minimalistycznym, otwartoźródłowym serwisem typu pastebin, w którym serwer nie ma żadnej wiedzy o przechowywanych danych. Dane są szyfrowane i deszyfrowane %sw przeglądarce%s z użyciem 256-bitowego klucza AES.",
-    "More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Więcej informacji na <a href=\"https://privatebin.info/\">stronie projektu</a>.",
-    "Because ignorance is bliss": "Ponieważ ignorancja jest cnotą",
+    "%s is a minimalist, open source online pastebin where the server has zero knowledge of stored data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s to minimalistyczny, otwartoźródłowy serwis typu pastebin, w którym serwer nie posiada żadnej wiedzy o przechowywanych danych. Dane są szyfrowane i deszyfrowywane %sw przeglądarce%s przy użyciu 256-bitowego klucza AES.",
+    "More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Więcej informacji znajdziesz na <a href=\"https://privatebin.info/\">stronie projektu</a>.",
+    "Because ignorance is bliss": "Bo niewiedza to błogosławieństwo",
     "Document does not exist, has expired or has been deleted.": "Dokument nie istnieje, wygasł lub został usunięty.",
-    "%s requires php %s or above to work. Sorry.": "%s wymaga PHP w wersji %s lub nowszej. Przykro mi.",
+    "%s requires php %s or above to work. Sorry.": "%s wymaga PHP w wersji %s lub nowszej.",
     "%s requires configuration section [%s] to be present in configuration file.": "%s wymaga obecności sekcji [%s] w pliku konfiguracyjnym.",
     "Please wait %d seconds between each post.": [
-        "Poczekaj %d sekundę pomiędzy każdą wklejką.",
-        "Poczekaj %d sekund pomiędzy każdą wklejką.",
-        "Poczekaj %d sekund pomiędzy każdą wklejką.",
-        "Poczekaj %d sekund pomiędzy każdą wklejką.",
-        "Poczekaj %d sekund pomiędzy każdą wklejką.",
-        "Poczekaj %d sekund pomiędzy każdą wklejką."
+        "Poczekaj %d sekundę przed kolejnym wklejeniem.",
+        "Poczekaj %d sekundy przed kolejnym wklejeniem.",
+        "Poczekaj %d sekund przed kolejnym wklejeniem.",
+        "Poczekaj %d sekund przed kolejnym wklejeniem.",
+        "Poczekaj %d sekund przed kolejnym wklejeniem."
     ],
     "Document is limited to %s of encrypted data.": "Dokument jest ograniczony do %s zaszyfrowanych danych.",
     "Invalid data.": "Nieprawidłowe dane.",
-    "You are unlucky. Try again.": "Miałeś pecha. Spróbuj ponownie.",
-    "Error saving comment. Sorry.": "Błąd przy zapisywaniu komentarza, sorry.",
-    "Error saving document. Sorry.": "Błąd podczas zapisywania dokumentu. Przepraszamy.",
-    "Invalid document ID.": "Nieprawidłowy ID dokumentu.",
-    "Document is not of burn-after-reading type.": "Dokument nie ulega autodestrukcji po przeczytaniu.",
-    "Wrong deletion token. Document was not deleted.": "Nieprawidłowy token usuwania. Dokument nie został usunięty.",
-    "Document was properly deleted.": "Dokument został prawidłowo usunięty.",
-    "JavaScript is required for %s to work. Sorry for the inconvenience.": "Do działania %sa jest wymagany JavaScript. Przepraszamy za niedogodność.",
-    "%s requires a modern browser to work.": "%s wymaga do działania nowoczesnej przeglądarki.",
-    "New": "Nowa",
-    "Create": "Dodaj",
+    "You are unlucky. Try again.": "Miałeś pecha — spróbuj ponownie.",
+    "Error saving comment. Sorry.": "Błąd podczas zapisywania komentarza.",
+    "Error saving document. Sorry.": "Błąd podczas zapisywania dokumentu.",
+    "Invalid document ID.": "Nieprawidłowy identyfikator dokumentu.",
+    "Document is not of burn-after-reading type.": "Dokument nie jest typu „zniszcz po przeczytaniu”.",
+    "Wrong deletion token. Document was not deleted.": "Nieprawidłowy token usunięcia. Dokument nie został usunięty.",
+    "Document was properly deleted.": "Dokument został pomyślnie usunięty.",
+    "JavaScript is required for %s to work. Sorry for the inconvenience.": "Do działania %s wymagany jest JavaScript. Przepraszamy za niedogodność.",
+    "%s requires a modern browser to work.": "%s wymaga nowoczesnej przeglądarki internetowej.",
+    "New": "Nowy",
+    "Create": "Utwórz",
     "Clone": "Sklonuj",
-    "Raw text": "Czysty tekst",
+    "Raw text": "Tekst niesformatowany",
     "Expires": "Wygasa za",
     "Burn after reading": "Zniszcz po przeczytaniu",
     "Open discussion": "Otwarta dyskusja",
-    "Password (recommended)": "Hasło (zalecane)",
+    "Password (recommended)": "Hasło (opcjonalnie, zalecane)",
     "Discussion": "Dyskusja",
     "Toggle navigation": "Przełącz nawigację",
     "%d seconds": [
-        "%d second",
-        "%d second",
-        "%d second",
-        "%d second",
-        "%d second",
-        "%d second"
+        "%d sekonda",
+        "%d sekondy",
+        "%d sekond",
+        "%d sekond",
+        "%d sekond",
+        "%d sekond",
+        "%d sekond",
+        "%d sekond"
     ],
     "%d minutes": [
+        "%d minuta",
+        "%d minuty",
         "%d minut",
         "%d minut",
         "%d minut",
@@ -53,73 +56,79 @@
     ],
     "%d hours": [
         "%d godzina",
-        "%d godzina",
-        "%d godzinę",
-        "%d godzinę",
-        "%d godzinę",
-        "%d godzinę"
+        "%d godziny",
+        "%d godzin",
+        "%d godzin",
+        "%d godzin",
+        "%d godzin",
+        "%d godzin",
+        "%d godzin"
     ],
     "%d days": [
         "%d dzień",
-        "%d dzień",
-        "%d dzień",
-        "%d dzień",
-        "%d dzień",
-        "%d dzień"
+        "%d dni",
+        "%d dni",
+        "%d dni",
+        "%d dni",
+        "%d dni",
+        "%d dni",
+        "%d dni"
     ],
     "%d weeks": [
         "%d tydzień",
-        "%d tydzień",
-        "%d tydzień",
-        "%d tydzień",
-        "%d tydzień",
-        "%d tydzień"
+        "%d tydgodnie",
+        "%d tydgodni",
+        "%d tydgodni",
+        "%d tydgodni",
+        "%d tydgodni",
+        "%d tydgodni",
+        "%d tydgodni"
     ],
     "%d months": [
         "%d miesiąc",
-        "%d miesiąc",
-        "%d miesiąc",
-        "%d miesiąc",
-        "%d miesiąc",
-        "%d miesiąc"
+        "%d miesiące",
+        "%d miesiący",
+        "%d miesiący",
+        "%d miesiący",
+        "%d miesiący",
+        "%d miesiący",
+        "%d miesiący"
     ],
     "%d years": [
         "%d rok",
-        "%d rok",
-        "%d rok",
-        "%d rok",
-        "%d rok",
-        "%d rok"
+        "%d lata",
+        "%d lat",
+        "%d lat",
+        "%d lat",
+        "%d lat",
+        "%d lat",
+        "%d lat"
     ],
-    "Never": "nigdy",
-    "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Notka: To jest usługa testowa. Dane mogą zostać usunięte w dowolnym momencie. Kociątka umrą, jeśli nadużyjesz tej usługi.",
+    "Never": "Nigdy",
+    "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Uwaga: to usługa testowa — dane mogą zostać usunięte w dowolnym momencie. Nie nadużywaj jej, bo ucierpią kotki 🐱.",
     "This document will expire in %d seconds.": [
-        "Ten dokument wygaśnie za %d sekundę.",
-        "Ten dokument wygaśnie za %d sekund.",
-        "Ten dokument wygaśnie za %d sekund.",
+        "Ten dokument wygaśnie za sekundę.",
+        "Ten dokument wygaśnie za %d sekundy.",
         "Ten dokument wygaśnie za %d sekund.",
         "Ten dokument wygaśnie za %d sekund.",
         "Ten dokument wygaśnie za %d sekund."
     ],
     "This document will expire in %d minutes.": [
-        "Ten dokument wygaśnie za %d minutę.",
-        "Ten dokument wygaśnie za %d minut.",
-        "Ten dokument wygaśnie za %d minut.",
+        "Ten dokument wygaśnie za minutę.",
+        "Ten dokument wygaśnie za %d minuty.",
         "Ten dokument wygaśnie za %d minut.",
         "Ten dokument wygaśnie za %d minut.",
         "Ten dokument wygaśnie za %d minut."
     ],
     "This document will expire in %d hours.": [
         "Ten dokument wygaśnie za godzinę.",
-        "Ten dokument wygaśnie za %d godzin.",
-        "Ten dokument wygaśnie za %d godzin.",
+        "Ten dokument wygaśnie za %d godziny.",
         "Ten dokument wygaśnie za %d godzin.",
         "Ten dokument wygaśnie za %d godzin.",
         "Ten dokument wygaśnie za %d godzin."
     ],
     "This document will expire in %d days.": [
-        "Ten dokument wygaśnie za %d dzień.",
-        "Ten dokument wygaśnie za %d dni.",
+        "Ten dokument wygaśnie za dzień.",
         "Ten dokument wygaśnie za %d dni.",
         "Ten dokument wygaśnie za %d dni.",
         "Ten dokument wygaśnie za %d dni.",
@@ -127,34 +136,33 @@
     ],
     "This document will expire in %d months.": [
         "Ten dokument wygaśnie za miesiąc.",
-        "Ten dokument wygaśnie za %d miesięcy.",
-        "Ten dokument wygaśnie za %d miesięcy.",
+        "Ten dokument wygaśnie za %d miesiące.",
         "Ten dokument wygaśnie za %d miesięcy.",
         "Ten dokument wygaśnie za %d miesięcy.",
         "Ten dokument wygaśnie za %d miesięcy."
     ],
-    "Please enter the password for this document:": "Wprowadź hasło dla tego dokumentu:",
+    "Please enter the password for this document:": "Podaj hasło do tego dokumentu:",
     "Could not decrypt data (Wrong key?)": "Nie udało się odszyfrować danych (zły klucz?)",
-    "Could not delete the document, it was not stored in burn after reading mode.": "Nie można usunąć dokumentu, nie została zapisana w trybie zniszczenia po przeczytaniu.",
-    "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "TYLKO DO TWOJEGO WGLĄDU. Nie zamykaj tego okna, ta wiadomość nie będzie mogła być wyświetlona ponownie.",
-    "Could not decrypt comment; Wrong key?": "Nie udało się odszyfrować komentarza; zły klucz?",
+    "Could not delete the document, it was not stored in burn after reading mode.": "Nie można usunąć dokumentu — nie był zapisany w trybie autodestrukcji po przeczytaniu.",
+    "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "TYLKO DO TWOJEGO WGLĄDU. Nie zamykaj tego okna — tej wiadomości nie będzie można ponownie wyświetlić.",
+    "Could not decrypt comment; Wrong key?": "Nie udało się odszyfrować komentarza (zły klucz?)",
     "Reply": "Odpowiedz",
-    "Anonymous": "Anonim",
-    "Avatar generated from IP address": "Anonimowy avatar (Vizhash z adresu IP)",
+    "Anonymous": "Anonimowy użytkownik",
+    "Avatar generated from IP address": "Awatar wygenerowany z adresu IP",
     "Add comment": "Dodaj komentarz",
-    "Optional nickname…": "Opcjonalny nick…",
+    "Optional nickname…": "Opcjonalny pseudonim…",
     "Post comment": "Wyślij komentarz",
     "Sending comment…": "Wysyłanie komentarza…",
-    "Comment posted.": "Wysłano komentarz.",
+    "Comment posted.": "Komentarz został dodany.",
     "Could not refresh display: %s": "Nie można odświeżyć widoku: %s",
     "unknown status": "nieznany status",
     "server error or not responding": "błąd serwera lub brak odpowiedzi",
     "Could not post comment: %s": "Nie udało się wysłać komentarza: %s",
     "Sending document…": "Wysyłanie dokumentu…",
-    "Your document is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit <kbd>Ctrl</kbd>+<kbd>c</kbd> to copy)</span>": "Twój dokument to <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(wciśnij <kbd>Ctrl</kbd>+<kbd>c</kbd> aby skopiować)</span>",
-    "Delete data": "Skasuj dane",
+    "Your document is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit <kbd>Ctrl</kbd>+<kbd>c</kbd> to copy)</span>": "Twój dokument: <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Użyj <kbd>Ctrl</kbd>+<kbd>c</kbd>, aby skopiować)</span>",
+    "Delete data": "Usuń dane",
     "Could not create document: %s": "Nie można utworzyć dokumentu: %s",
-    "Cannot decrypt document: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Nie można odszyfrować dokumentu: Brak klucza deszyfrującego w adresie URL (Czy użyto przekierowania lub skracacza adresów URL, który usuwa część adresu URL?)",
+    "Cannot decrypt document: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Nie można odszyfrować dokumentu — brakuje klucza deszyfrującego w adresie URL (czy użyto przekierowania lub skracacza adresów, który usuwa część linku?)",
     "B": "B",
     "kB": "kB",
     "MB": "MB",
@@ -165,70 +173,70 @@
     "ZB": "ZB",
     "YB": "YB",
     "Format": "Format",
-    "Plain Text": "Czysty tekst",
-    "Source Code": "Kod źródłowy",
-    "Markdown": "Znakowanie",
+    "Plain Text": "Tekst niesformatowany",
+    "Source Code": "Kod źródłowy (source code)",
+    "Markdown": "Markdown",
     "Download attachment": "Pobierz załącznik",
     "Cloned: '%s'": "Sklonowano: '%s'",
-    "The cloned file '%s' was attached to this document.": "Sklonowany plik '%s' był dołączony do tego dokumentu.",
+    "The cloned file '%s' was attached to this document.": "Sklonowany plik '%s' został dołączony do tego dokumentu.",
     "Attach a file": "Załącz plik",
-    "alternatively drag & drop a file or paste an image from the clipboard": "Alternatywnie przeciągnij i upuść plik albo wklej obraz ze schowka",
-    "File too large, to display a preview. Please download the attachment.": "Plik zbyt duży aby wyświetlić podgląd. Proszę pobrać załącznik.",
+    "alternatively drag & drop a file or paste an image from the clipboard": "Lub przeciągnij i upuść plik, albo wklej obraz ze schowka.",
+    "File too large, to display a preview. Please download the attachment.": "Plik jest zbyt duży, by wyświetlić podgląd. Pobierz załącznik.",
     "Remove attachment": "Usuń załącznik",
-    "Your browser does not support uploading encrypted files. Please use a newer browser.": "Twoja przeglądarka nie wspiera wysyłania zaszyfrowanych plików. Użyj nowszej przeglądarki.",
+    "Your browser does not support uploading encrypted files. Please use a newer browser.": "Twoja przeglądarka nie obsługuje wysyłania zaszyfrowanych plików. Użyj nowszej wersji przeglądarki.",
     "Invalid attachment.": "Nieprawidłowy załącznik.",
     "Options": "Opcje",
     "Shorten URL": "Skróć adres URL",
-    "Editor": "Edytować",
+    "Editor": "Edytor",
     "Preview": "Podgląd",
-    "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s wymaga zmiennej PATH zakończonej w \"%s\". Zaktualizuj zmienną PATH w index.php.",
+    "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s wymaga, aby zmienna PATH kończyła się na \"%s\". Zaktualizuj PATH w pliku index.php.",
     "Decrypt": "Odszyfruj",
     "Enter password": "Wpisz hasło",
-    "Loading…": "Wczytywanie…",
+    "Loading…": "Ładowanie…",
     "Decrypting document…": "Odszyfrowywanie dokumentu…",
     "Preparing new document…": "Przygotowywanie nowego dokumentu…",
-    "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "W przypadku gdy ten komunikat nigdy nie znika, proszę spójrz na <a href=\"%s\">to FAQ aby rozwiązać problem</a> (po angielsku).",
-    "+++ no document text +++": "+++ brak tekstu dokumentu +++",
-    "Could not get document data: %s": "Nie można uzyskać danych dokumentu: %s",
+    "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "Jeśli ten komunikat nie znika, zajrzyj do <a href=\"%s\">FAQ</a>, aby znaleźć rozwiązanie problemu.",
+    "+++ no document text +++": "+++ brak treści dokumentu +++",
+    "Could not get document data: %s": "Nie można pobrać danych dokumentu: %s",
     "QR code": "Kod QR",
-    "This website is using an insecure HTTP connection! Please use it only for testing.": "Ta witryna używa niezabezpieczonego połączenia HTTP! Używaj jej wyłącznie do testowania.",
-    "For more information <a href=\"%s\">see this FAQ entry</a>.": "Aby uzyskać więcej informacji, <a href=\"%s\">sprawdź to często zadawane pytanie</a>.",
-    "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Twoja przeglądarka może wymagać połączenia HTTPS, aby obsługiwać API WebCrypto. Spróbuj <a href=\"%s\">przełączyć się na HTTPS</a>.",
-    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Twoja przeglądarka nie obsługuje WebAssembly używanego do kompresji zlib. Możesz utworzyć nieskompresowane dokumenty, ale nie możesz odczytać skompresowanych.",
+    "This website is using an insecure HTTP connection! Please use it only for testing.": "Ta strona korzysta z niezabezpieczonego połączenia HTTP! Używaj jej tylko do testów.",
+    "For more information <a href=\"%s\">see this FAQ entry</a>.": "Więcej informacji znajdziesz w <a href=\"%s\">tym wpisie FAQ</a>.",
+    "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Twoja przeglądarka może wymagać połączenia HTTPS, aby obsługiwać WebCrypto API. Spróbuj <a href=\"%s\">przełączyć się na HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Twoja przeglądarka nie obsługuje WebAssembly (używanego do kompresji zlib). Możesz tworzyć nieskompresowane dokumenty, ale nie odczytasz skompresowanych.",
     "waiting on user to provide a password": "oczekiwanie na podanie hasła przez użytkownika",
-    "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Błąd odszyfrowywania danych. Czy wprowadzono niepoprawne hasło? Spróbuj ponownie, używając przycisku na górze.",
-    "Retry": "Ponów",
-    "Showing raw text…": "Pokazywanie nieprzetworzonego tekstu…",
+    "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Nie udało się odszyfrować danych. Czy podałeś błędne hasło? Spróbuj ponownie, używając przycisku u góry.",
+    "Retry": "Spróbuj ponownie",
+    "Showing raw text…": "Wyświetlanie surowego tekstu…",
     "Notice:": "Uwaga:",
-    "This link will expire after %s.": "Ten odnośnik wygaśnie po %s.",
-    "This link can only be accessed once, do not use back or refresh button in your browser.": "Tego odnośnika można użyć tylko jeden raz. Nie cofaj ani nie odświeżaj strony w przeglądarce.",
-    "Link:": "Odnośnik:",
-    "Recipient may become aware of your timezone, convert time to UTC?": "Odbiorca może uzyskać informację o twojej strefie czasowej. Zamienić czas na UTC?",
+    "This link will expire after %s.": "Ten link wygaśnie po %s.",
+    "This link can only be accessed once, do not use back or refresh button in your browser.": "Tego linku można użyć tylko raz — nie cofaj ani nie odświeżaj strony.",
+    "Link:": "Link:",
+    "Recipient may become aware of your timezone, convert time to UTC?": "Odbiorca może poznać Twoją strefę czasową. Czy chcesz zamienić czas na UTC?",
     "Use Current Timezone": "Użyj bieżącej strefy czasowej",
     "Convert To UTC": "Zamień na UTC",
     "Close": "Zamknij",
     "Encrypted note on %s": "Zaszyfrowana notatka na %s",
-    "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Naciśnij tej odnośnik, aby zobaczyć notatkę. Każda osoba z dostępem do tego adresu URL również może zobaczyć tę notatkę.",
-    "URL shortener may expose your decrypt key in URL.": "Skrócenie adresu URL może ujawnić Twój klucz odszyfrowujący w adresie URL.",
-    "URL shortener is enabled by default.": "URL shortener is enabled by default.",
+    "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Odwiedź ten link, aby zobaczyć notatkę. Każdy, kto ma ten adres URL, również będzie mógł ją zobaczyć.",
+    "URL shortener may expose your decrypt key in URL.": "Skracacz adresów może ujawnić Twój klucz deszyfrujący w URL.",
+    "URL shortener is enabled by default.": "Skracacz adresów URL jest domyślnie włączony.",
     "Save document": "Zapisz dokument",
-    "Your IP is not authorized to create documents.": "Twój adres IP nie jest autoryzowany do tworzenia dokumentów.",
-    "Trying to shorten a URL that isn't pointing at our instance.": "Próba skrócenia adresu URL wskazującego na inną instancję.",
-    "Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.": "Błąd wywoływania YOURLS. Możliwy błąd konfiguracji, taki jak błędne lub brakujące \"apiurl\" lub \"signature\".",
-    "Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.": "Błąd przetwarzania odpowiedzi YOURLS.",
-    "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.": "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.",
-    "This secret message can only be displayed once. Would you like to see it now?": "Tę tajną wiadomość możesz zobaczyć tylko jeden raz. Czy chcesz to zrobić teraz?",
+    "Your IP is not authorized to create documents.": "Twój adres IP nie jest uprawniony do tworzenia dokumentów.",
+    "Trying to shorten a URL that isn't pointing at our instance.": "Próba skrócenia adresu URL, który nie wskazuje na nasz serwer.",
+    "Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.": "Błąd proxy: adres URL proxy jest pusty. Może to wynikać z błędnej konfiguracji (brak lub błędne klucze).",
+    "Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.": "Błąd proxy: problem z przetwarzaniem odpowiedzi. Sprawdź konfigurację (brakujące lub błędne klucze).",
+    "Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.": "Błąd proxy: nieprawidłowa odpowiedź. Możliwy problem z konfiguracją lub chwilowa awaria.",
+    "This secret message can only be displayed once. Would you like to see it now?": "Tę tajną wiadomość można wyświetlić tylko raz. Czy chcesz ją zobaczyć teraz?",
     "Yes, see it": "Tak, pokaż",
-    "Dark Mode": "Ciemny motyw",
-    "Error compressing document, due to missing WebAssembly support.": "Wystąpił błąd podczas kompresji dokumentu z powodu braku obsługi WebAssembly.",
-    "Error decompressing document, your browser does not support WebAssembly. Please use another browser to view this document.": "Wystąpił błąd podczas dekompresji dokumentu. Twoja przeglądarka nie obsługuje WebAssembly. Użyj innej przeglądarki, aby wyświetlić ten dokument.",
+    "Dark Mode": "Tryb ciemny",
+    "Error compressing document, due to missing WebAssembly support.": "Błąd kompresji dokumentu z powodu braku obsługi WebAssembly.",
+    "Error decompressing document, your browser does not support WebAssembly. Please use another browser to view this document.": "Błąd dekompresji dokumentu — Twoja przeglądarka nie obsługuje WebAssembly. Użyj innej przeglądarki, aby go otworzyć.",
     "Start over": "Zacznij od nowa",
-    "Document copied to clipboard": "Dokument został skopiowany do schowka",
-    "To copy document press on the copy button or use the clipboard shortcut <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>": "Aby skopiować dokument, naciśnij przycisk kopiowania lub użyj skrótu do schowka <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>",
+    "Document copied to clipboard": "Dokument skopiowany do schowka",
+    "To copy document press on the copy button or use the clipboard shortcut <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>": "Aby skopiować dokument, kliknij przycisk kopiowania lub użyj skrótu <kbd>Ctrl</kbd>+<kbd>c</kbd>/<kbd>Cmd</kbd>+<kbd>c</kbd>",
     "Copy link": "Kopiuj link",
-    "Link copied to clipboard": "Link został skopiowany do schowka",
-    "Document text": "Tekst dokumentu",
-    "Tabulator key serves as character (Hit <kbd>Ctrl</kbd>+<kbd>m</kbd> or <kbd>Esc</kbd> to toggle)": "Klawisz Tabulatora służy jako znak (przytrzymaj <kbd>Ctrl</kbd>+<kbd>m</kbd> lub <kbd>Esc</kbd> aby przełączać)",
+    "Link copied to clipboard": "Link skopiowany do schowka",
+    "Document text": "Treść dokumentu",
+    "Tabulator key serves as character (Hit <kbd>Ctrl</kbd>+<kbd>m</kbd> or <kbd>Esc</kbd> to toggle)": "Klawisz Tabulatora służy jako znak (użyj <kbd>Ctrl</kbd>+<kbd>m</kbd> lub <kbd>Esc</kbd>, aby przełączyć tryb)",
     "Show password": "Show password",
     "Hide password": "Hide password",
     "Theme": "Motyw"

+ 137 - 0
js/test/emailTemplateTest.js

@@ -0,0 +1,137 @@
+'use strict';
+var common = require('../common');
+const assert = require('assert');
+
+// DOM builder that mirrors bootstrap5.php navbar
+function buildEmailDomNoShortUrl() {
+    $('body').html(
+        // TopNav expects initially hidden #emaillink BUTTON.
+        '<nav><div id="navbar"><ul>' +
+          '<li>' +
+            '<button id="clonebutton" type="button" class="btn btn-warning navbar-btn">' +
+              '<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> Clone' +
+            '</button>' +
+          '</li>' +
+          '<li>' +
+            '<button id="emaillink" type="button" class="hidden btn btn-secondary">Email</button>' +
+          '</li>' +
+        '</ul></div></nav>' +
+        '<input id="burnafterreadingoption" type="checkbox">'
+    );
+}
+
+// DOM builder that adds the shortener result block
+function buildEmailDomWithShortUrl() {
+    buildEmailDomNoShortUrl();
+    $('body').html(
+        // TopNav expectsinitially hidden #emaillink BUTTON.
+        '<nav><div id="navbar"><ul>' +
+          '<li>' +
+            '<button id="clonebutton" type="button" class="btn btn-warning navbar-btn">' +
+              '<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> Clone' +
+            '</button>' +
+          '</li>' +
+          '<li>' +
+            '<button id="emaillink" type="button" class="hidden btn btn-secondary">Email</button>' +
+          '</li>' +
+        '</ul></div></nav>' +
+        '<input id="burnafterreadingoption" type="checkbox">' +
+        '<div id="pastelink">Your document is ' +
+          `<a id="pasteurl" href="'https://short.example/xYz'">'https://short.example/xYz'</a> ` +
+          '<span id="copyhint">(Hit <kbd>Ctrl</kbd>+<kbd>c</kbd> to copy)</span>' +
+        '</div>'
+    );
+}
+
+
+function stubWinOpen($element) {
+    const win = $element[0].ownerDocument.defaultView;
+
+    // Some helpers in privatebin.js expect a global document.
+    global.document = win.document;
+
+    let openedUrl = null;
+    const origOpen = win.open;
+
+    // Prefer simple assignment; if blocked, fall back to defineProperty.
+    try {
+        win.open = function (url) {
+            openedUrl = url;
+            return {};
+        };
+    } catch {
+        Object.defineProperty(win, 'open', {
+            value: function (url) {
+                openedUrl = url;
+                return {};
+            },
+            configurable: true,
+            writable: true
+        });
+    }
+
+    return {
+        getUrl: () => openedUrl,
+        restore: () => { try { win.open = origOpen; } catch {} },
+        win
+    };
+}
+
+
+// Extract and decode the body from a "mailto:?body=..." URL.
+function extractMailtoBody(mailtoUrl) {
+    assert.ok(/^mailto:\?body=/.test(mailtoUrl), 'expected a mailto:?body= URL');
+    return decodeURIComponent(mailtoUrl.replace(/^mailto:\?body=/, ''));
+}
+
+describe('Email - mail body content (short URL vs. fallback)', function () {
+    before(function () {
+        cleanup(); // provided by common
+    });
+
+    it('Uses the short URL when #pasteurl is present and never includes "undefined"', function () {
+        buildEmailDomWithShortUrl();               // with #pastelink/#pasteurl
+        $.PrivateBin.TopNav.init();
+        $.PrivateBin.TopNav.showEmailButton(0);
+
+        const $emailBtn = $('#emaillink');
+        assert.ok(!$emailBtn.hasClass('hidden'), '#emaillink should be visible after showEmailButton');
+
+        const { getUrl, restore } = stubWinOpen($emailBtn);
+        try {
+            $emailBtn.trigger('click');
+            const openedUrl = getUrl();
+            assert.ok(openedUrl, 'window.open should have been called');
+
+            const body = extractMailtoBody(openedUrl);
+            assert.ok(body.includes('https://short.example/xYz'), 'email body should include the short URL');
+            assert.ok(!body.includes('undefined'), 'email body must not contain "undefined"');
+        } finally {
+            restore();
+            cleanup();
+        }
+    });
+
+    it('Falls back to window.location.href when #pasteurl is absent and never includes "undefined"', function () {
+        buildEmailDomNoShortUrl();      // No #pasteurl
+        $.PrivateBin.TopNav.init();
+        $.PrivateBin.TopNav.showEmailButton(0);
+
+        const $emailBtn = $('#emaillink');
+        assert.ok(!$emailBtn.hasClass('hidden'), '#emaillink should be visible after showEmailButton');
+
+        const { getUrl, restore, win } = stubWinOpen($emailBtn);
+        try {
+            $emailBtn.trigger('click');
+            const openedUrl = getUrl();
+            assert.ok(openedUrl, 'window.open should have been called');
+
+            const body = extractMailtoBody(openedUrl);
+            assert.ok(body.includes(win.location.href), 'email body should include the fallback page URL');
+            assert.ok(!body.includes('undefined'), 'email body must not contain "undefined"');
+        } finally {
+            restore();
+            cleanup();
+        }
+    });
+});

+ 6 - 6
tst/I18nTest.php

@@ -114,13 +114,13 @@ class I18nTest extends TestCase
         I18n::loadTranslations();
         $this->assertEquals('pl', I18n::getLanguage(), 'browser language pl');
         $this->assertEquals('1 godzina', I18n::_('%d hours', 1), '1 hour in Polish');
-        $this->assertEquals('2 godzina', I18n::_('%d hours', 2), '2 hours in Polish');
-        $this->assertEquals('12 godzinę', I18n::_('%d hours', 12), '12 hours in Polish');
-        $this->assertEquals('22 godzina', I18n::_('%d hours', 22), '22 hours in Polish');
-        $this->assertEquals('1 minut',  I18n::_('%d minutes', 1), '1 minute in Polish');
-        $this->assertEquals('3 minut',  I18n::_('%d minutes', 3), '3 minutes in Polish');
+        $this->assertEquals('2 godziny', I18n::_('%d hours', 2), '2 hours in Polish');
+        $this->assertEquals('12 godzin', I18n::_('%d hours', 12), '12 hours in Polish');
+        $this->assertEquals('22 godziny', I18n::_('%d hours', 22), '22 hours in Polish');
+        $this->assertEquals('1 minuta',  I18n::_('%d minutes', 1), '1 minute in Polish');
+        $this->assertEquals('3 minuty',  I18n::_('%d minutes', 3), '3 minutes in Polish');
         $this->assertEquals('13 minut',  I18n::_('%d minutes', 13), '13 minutes in Polish');
-        $this->assertEquals('23 minut',  I18n::_('%d minutes', 23), '23 minutes in Polish');
+        $this->assertEquals('23 minuty',  I18n::_('%d minutes', 23), '23 minutes in Polish');
     }
 
     public function testBrowserLanguageRuDetection()