Преглед изворни кода

Merge branch 'master' into advisory-fix-1

El RIDO пре 2 недеља
родитељ
комит
dfbfd02f19
14 измењених фајлова са 118 додато и 42 уклоњено
  1. 4 4
      .github/dependabot.yml
  2. 3 0
      CHANGELOG.md
  3. 1 1
      CREDITS.md
  4. 2 2
      i18n/pl.json
  5. 13 13
      i18n/sl.json
  6. 4 4
      js/package-lock.json
  7. 1 1
      js/package.json
  8. 14 6
      js/privatebin.js
  9. 11 0
      js/test/I18n.js
  10. 40 0
      js/test/TopNav.js
  11. 1 1
      lib/Configuration.php
  12. 1 1
      lib/I18n.php
  13. 1 1
      tpl/bootstrap5.php
  14. 22 8
      tst/I18nTest.php

+ 4 - 4
.github/dependabot.yml

@@ -12,7 +12,7 @@ updates:
     directory: "/"
     directory: "/"
     schedule:
     schedule:
       interval: "daily"
       interval: "daily"
- - package-ecosystem: "npm"
-   directory: "/js"
-   schedule:
-     interval: "daily"
+  - package-ecosystem: "npm"
+    directory: "/js"
+    schedule:
+      interval: "daily"

+ 3 - 0
CHANGELOG.md

@@ -2,6 +2,9 @@
 
 
 ## 2.1.1 (not yet released)
 ## 2.1.1 (not yet released)
 * CHANGED: Check `sizelimit` over full request, before attempting decoding
 * CHANGED: Check `sizelimit` over full request, before attempting decoding
+* FIXED: Creating a document failed with a JavaScript error when the `discussion` or `password` option is disabled, as the top navigation accessed the omitted elements
+* FIXED: Slovenian plural forms were stored in the wrong order, i.e. "1 uri" instead of "1 ura" for one hour (#1951)
+* FIXED: Lithuanian plural formula selected the "few" form for numbers ending in 0 above 10, i.e. "20 sekundės" instead of "20 sekundžių" (#1956)
 
 
 ## 2.1.0 (2026-10-18)
 ## 2.1.0 (2026-10-18)
 * ADDED: Added `shortenviachhoto` endpoint with an `chhoto` configuration section
 * ADDED: Added `shortenviachhoto` endpoint with an `chhoto` configuration section

+ 1 - 1
CREDITS.md

@@ -39,7 +39,7 @@
 ## Translations
 ## Translations
 
 
 * Hexalyse - French
 * Hexalyse - French
-* Kuba Niewiarowski - Polish
+* Kuba Niewiarowski, Mariusz Krzaczkowski - Polish
 * Gabbalo - German
 * Gabbalo - German
 * Draky50110 - French
 * Draky50110 - French
 * Filip Muki Dobranić - Slovene
 * Filip Muki Dobranić - Slovene

+ 2 - 2
i18n/pl.json

@@ -232,8 +232,8 @@
     "Show password": "Pokaż hasło",
     "Show password": "Pokaż hasło",
     "Hide password": "Ukryj hasło",
     "Hide password": "Ukryj hasło",
     "Theme": "Motyw",
     "Theme": "Motyw",
-    "Copy": "Copy",
+    "Copy": "Kopiuj",
     "Cmd": "Cmd",
     "Cmd": "Cmd",
     "Ctrl": "Ctrl",
     "Ctrl": "Ctrl",
-    "Hit %s+s to create the document, storing it on %s": "Hit %s+s to create the document, storing it on %s"
+    "Hit %s+s to create the document, storing it on %s": "Naciśnij %s+s, aby utworzyć dokument i zapisać go w %s"
 }
 }

+ 13 - 13
i18n/sl.json

@@ -7,11 +7,11 @@
     "%s requires php %s or above to work. Sorry.": "Oprosti, %s za delovanje potrebuje vsaj php %s.",
     "%s requires php %s or above to work. Sorry.": "Oprosti, %s za delovanje potrebuje vsaj php %s.",
     "%s requires configuration section [%s] to be present in configuration file.": "%s potrebuje sekcijo konfiguracij [%s] v konfiguracijski datoteki.",
     "%s requires configuration section [%s] to be present in configuration file.": "%s potrebuje sekcijo konfiguracij [%s] v konfiguracijski datoteki.",
     "Please wait %d seconds between each post.": [
     "Please wait %d seconds between each post.": [
+        "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekundo pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekundo pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekundi pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekundi pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekunde pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekunde pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo.",
-        "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo.",
         "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo."
         "Prosim počakaj vsaj %d sekund pred vsako naslednjo objavo."
     ],
     ],
     "Document is limited to %s of encrypted data.": "Velikost prilepka je omejena na %s zakodiranih podatkov.",
     "Document is limited to %s of encrypted data.": "Velikost prilepka je omejena na %s zakodiranih podatkov.",
@@ -36,101 +36,101 @@
     "Discussion": "Razprava",
     "Discussion": "Razprava",
     "Toggle navigation": "Preklopi navigacijo",
     "Toggle navigation": "Preklopi navigacijo",
     "%d seconds": [
     "%d seconds": [
+        "%d sekund",
         "%d sekunda",
         "%d sekunda",
         "%d sekundi",
         "%d sekundi",
         "%d sekunde",
         "%d sekunde",
         "%d sekund",
         "%d sekund",
-        "%d sekund",
         "%d sekund"
         "%d sekund"
     ],
     ],
     "%d minutes": [
     "%d minutes": [
+        "%d minut",
         "%d minuta",
         "%d minuta",
         "%d minuti",
         "%d minuti",
         "%d minute",
         "%d minute",
         "%d minut",
         "%d minut",
-        "%d minut",
         "%d minut"
         "%d minut"
     ],
     ],
     "%d hours": [
     "%d hours": [
+        "%d ur",
         "%d ura",
         "%d ura",
         "%d uri",
         "%d uri",
         "%d ure",
         "%d ure",
         "%d ur",
         "%d ur",
-        "%d ur",
         "%d ur"
         "%d ur"
     ],
     ],
     "%d days": [
     "%d days": [
+        "%d dni",
         "%d dan",
         "%d dan",
         "%d dneva",
         "%d dneva",
         "%d dnevi",
         "%d dnevi",
         "%d dni",
         "%d dni",
-        "%d dni",
         "%d dni"
         "%d dni"
     ],
     ],
     "%d weeks": [
     "%d weeks": [
+        "%d tednov",
         "%d teden",
         "%d teden",
         "%d tedna",
         "%d tedna",
         "%d tedni",
         "%d tedni",
         "%d tednov",
         "%d tednov",
-        "%d tednov",
         "%d tednov"
         "%d tednov"
     ],
     ],
     "%d months": [
     "%d months": [
+        "%d mesecev",
         "%d mesec",
         "%d mesec",
         "%d meseca",
         "%d meseca",
         "%d meseci",
         "%d meseci",
         "%d mesecev",
         "%d mesecev",
-        "%d mesecev",
         "%d mesecev"
         "%d mesecev"
     ],
     ],
     "%d years": [
     "%d years": [
+        "%d let",
         "%d leto",
         "%d leto",
         "%d leti",
         "%d leti",
         "%d leta",
         "%d leta",
         "%d let",
         "%d let",
-        "%d let",
         "%d let"
         "%d let"
     ],
     ],
     "Never": "Nikoli",
     "Never": "Nikoli",
     "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Ne pozabi: To je testna storitev: Podatki so lahko kadarkoli pobrisani. Mucki bodo umrli, če boš zlorabljala to storitev.",
     "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Ne pozabi: To je testna storitev: Podatki so lahko kadarkoli pobrisani. Mucki bodo umrli, če boš zlorabljala to storitev.",
     "This document will expire in %d seconds.": [
     "This document will expire in %d seconds.": [
+        "Ta dokument bo potekel čez %d sekund.",
         "Ta dokument bo potekel čez %d sekundo.",
         "Ta dokument bo potekel čez %d sekundo.",
         "Ta dokument bo potekel čez %d sekundi.",
         "Ta dokument bo potekel čez %d sekundi.",
         "Ta dokument bo potekel čez %d sekunde.",
         "Ta dokument bo potekel čez %d sekunde.",
         "Ta dokument bo potekel čez %d sekund.",
         "Ta dokument bo potekel čez %d sekund.",
-        "Ta dokument bo potekel čez %d sekund.",
         "Ta dokument bo potekel čez %d sekund."
         "Ta dokument bo potekel čez %d sekund."
     ],
     ],
     "This document will expire in %d minutes.": [
     "This document will expire in %d minutes.": [
+        "Ta dokument bo potekel čez %d minut.",
         "Ta dokument bo potekel čez %d minuto.",
         "Ta dokument bo potekel čez %d minuto.",
         "Ta dokument bo potekel čez %d minuti.",
         "Ta dokument bo potekel čez %d minuti.",
         "Ta dokument bo potekel čez %d minute.",
         "Ta dokument bo potekel čez %d minute.",
-        "Ta dokument bo potekel čez %d minute.",
         "Ta dokument bo potekel čez %d minut.",
         "Ta dokument bo potekel čez %d minut.",
         "Ta dokument bo potekel čez %d minut."
         "Ta dokument bo potekel čez %d minut."
     ],
     ],
     "This document will expire in %d hours.": [
     "This document will expire in %d hours.": [
+        "Ta dokument bo potekel čez %d ur.",
         "Ta dokument bo potekel čez %d uro.",
         "Ta dokument bo potekel čez %d uro.",
         "Ta dokument bo potekel čez %d uri.",
         "Ta dokument bo potekel čez %d uri.",
         "Ta dokument bo potekel čez %d ure.",
         "Ta dokument bo potekel čez %d ure.",
-        "Ta dokument bo potekel čez %d ure.",
         "Ta dokument bo potekel čez %d ur.",
         "Ta dokument bo potekel čez %d ur.",
         "Ta dokument bo potekel čez %d ur."
         "Ta dokument bo potekel čez %d ur."
     ],
     ],
     "This document will expire in %d days.": [
     "This document will expire in %d days.": [
+        "Ta dokument bo potekel čez %d dni.",
         "Ta dokument bo potekel čez %d dan.",
         "Ta dokument bo potekel čez %d dan.",
         "Ta dokument bo potekel čez %d dneva.",
         "Ta dokument bo potekel čez %d dneva.",
         "Ta dokument bo potekel čez %d dni.",
         "Ta dokument bo potekel čez %d dni.",
         "Ta dokument bo potekel čez %d dni.",
         "Ta dokument bo potekel čez %d dni.",
-        "Ta dokument bo potekel čez %d dni.",
         "Ta dokument bo potekel čez %d dni."
         "Ta dokument bo potekel čez %d dni."
     ],
     ],
     "This document will expire in %d months.": [
     "This document will expire in %d months.": [
+        "Ta dokument bo potekel čez %d mesecev.",
         "Ta dokument bo potekel čez %d mesec.",
         "Ta dokument bo potekel čez %d mesec.",
         "Ta dokument bo potekel čez %d meseca.",
         "Ta dokument bo potekel čez %d meseca.",
         "Ta dokument bo potekel čez %d mesece.",
         "Ta dokument bo potekel čez %d mesece.",
         "Ta dokument bo potekel čez %d mesecev.",
         "Ta dokument bo potekel čez %d mesecev.",
-        "Ta dokument bo potekel čez %d mesecev.",
         "Ta dokument bo potekel čez %d mesecev."
         "Ta dokument bo potekel čez %d mesecev."
     ],
     ],
     "Please enter the password for this document:": "Prosim vnesi geslo tega prilepka:",
     "Please enter the password for this document:": "Prosim vnesi geslo tega prilepka:",

+ 4 - 4
js/package-lock.json

@@ -11,7 +11,7 @@
       "devDependencies": {
       "devDependencies": {
         "@peculiar/webcrypto": "^1.5.0",
         "@peculiar/webcrypto": "^1.5.0",
         "eslint": "^9.37.0",
         "eslint": "^9.37.0",
-        "fast-check": "^4.7.0",
+        "fast-check": "^4.10.2",
         "jsdom": "^26.1.0",
         "jsdom": "^26.1.0",
         "jsdom-global": "^3.0.2",
         "jsdom-global": "^3.0.2",
         "jshint": "^2.13.6",
         "jshint": "^2.13.6",
@@ -1580,9 +1580,9 @@
       }
       }
     },
     },
     "node_modules/fast-check": {
     "node_modules/fast-check": {
-      "version": "4.7.0",
-      "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz",
-      "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==",
+      "version": "4.10.2",
+      "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.10.2.tgz",
+      "integrity": "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw==",
       "dev": true,
       "dev": true,
       "funding": [
       "funding": [
         {
         {

+ 1 - 1
js/package.json

@@ -9,7 +9,7 @@
   "devDependencies": {
   "devDependencies": {
     "@peculiar/webcrypto": "^1.5.0",
     "@peculiar/webcrypto": "^1.5.0",
     "eslint": "^9.37.0",
     "eslint": "^9.37.0",
-    "fast-check": "^4.7.0",
+    "fast-check": "^4.10.2",
     "jsdom": "^26.1.0",
     "jsdom": "^26.1.0",
     "jsdom-global": "^3.0.2",
     "jsdom-global": "^3.0.2",
     "jshint": "^2.13.6",
     "jshint": "^2.13.6",

+ 14 - 6
js/privatebin.js

@@ -878,7 +878,7 @@ window.PrivateBin = (function () {
                 case 'th':
                 case 'th':
                     return 0;
                     return 0;
                 case 'lt':
                 case 'lt':
-                    return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
+                    return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20)) ? 1 : 2);
                 case 'pl':
                 case 'pl':
                     return n === 1 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
                     return n === 1 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
                 case 'ro':
                 case 'ro':
@@ -4333,8 +4333,12 @@ window.PrivateBin = (function () {
             expiration.classList.remove('hidden');
             expiration.classList.remove('hidden');
             formatter.classList.remove('hidden');
             formatter.classList.remove('hidden');
             newButton.classList.remove('hidden');
             newButton.classList.remove('hidden');
-            openDiscussionOption.classList.remove('hidden');
-            password.classList.remove('hidden');
+            if (openDiscussionOption) {
+                openDiscussionOption.classList.remove('hidden');
+            }
+            if (password) {
+                password.classList.remove('hidden');
+            }
             sendButton.classList.remove('hidden');
             sendButton.classList.remove('hidden');
 
 
             createButtonsDisplayed = true;
             createButtonsDisplayed = true;
@@ -4356,8 +4360,12 @@ window.PrivateBin = (function () {
             expiration.classList.add('hidden');
             expiration.classList.add('hidden');
             formatter.classList.add('hidden');
             formatter.classList.add('hidden');
             burnAfterReadingOption.classList.add('hidden');
             burnAfterReadingOption.classList.add('hidden');
-            openDiscussionOption.classList.add('hidden');
-            password.classList.add('hidden');
+            if (openDiscussionOption) {
+                openDiscussionOption.classList.add('hidden');
+            }
+            if (password) {
+                password.classList.add('hidden');
+            }
             if (attach) {
             if (attach) {
                 attach.classList.add('hidden');
                 attach.classList.add('hidden');
             }
             }
@@ -4571,7 +4579,7 @@ window.PrivateBin = (function () {
             if (openDiscussion) {
             if (openDiscussion) {
                 openDiscussion.checked = openDiscussionDefault;
                 openDiscussion.checked = openDiscussionDefault;
             }
             }
-            if (openDiscussionDefault || !burnAfterReadingDefault) {
+            if (openDiscussionOption && (openDiscussionDefault || !burnAfterReadingDefault)) {
                 openDiscussionOption.classList.remove('buttondisabled');
                 openDiscussionOption.classList.remove('buttondisabled');
             }
             }
             if (burnAfterReadingDefault || !openDiscussionDefault) {
             if (burnAfterReadingDefault || !openDiscussionDefault) {

+ 11 - 0
js/test/I18n.js

@@ -183,6 +183,17 @@ describe('I18n', function () {
                 }
                 }
             ));
             ));
         });
         });
+
+        it('routes Lithuanian cardinal forms by the last digit', () => {
+            PrivateBin.I18n.reset('lt');
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(1), 0);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(2), 1);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(10), 2);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(22), 1);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(110), 2);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(111), 2);
+            assert.strictEqual(PrivateBin.I18n.getPluralForm(120), 2);
+        });
     });
     });
 
 
     // loading of JSON via AJAX needs to be tested in the browser, this just mocks it
     // loading of JSON via AJAX needs to be tested in the browser, this just mocks it

+ 40 - 0
js/test/TopNav.js

@@ -125,6 +125,31 @@ describe('TopNav', function () {
                 assert.ok(result);
                 assert.ok(result);
             }
             }
         );
         );
+
+        it(
+            'tolerates missing optional elements (discussion & password disabled)',
+            function () {
+                // with discussion = false and password = false in the
+                // configuration, the template omits these elements entirely
+                document.documentElement.innerHTML =
+                    '<nav><div id="navbar"><ul><li><button id="newbutton" ' +
+                    'type="button" class="hidden">New</button></li><li><a ' +
+                    'id="expiration" href="#" class="hidden">Expiration</a>' +
+                    '</li><li><div id="burnafterreadingoption" class="hidden">' +
+                    'Burn after reading</div></li><li>' +
+                    '<a id="formatter" href="#" class="hidden">Format</a>' +
+                    '</li><li><button id="sendbutton" type="button" ' +
+                    'class="hidden">Create</button></li></ul></div></nav>';
+                PrivateBin.TopNav.init();
+                assert.doesNotThrow(() => PrivateBin.TopNav.showCreateButtons());
+                assert.ok(!query('#sendbutton').classList.contains('hidden'));
+                assert.ok(!query('#burnafterreadingoption').classList.contains('hidden'));
+                assert.doesNotThrow(() => PrivateBin.TopNav.hideCreateButtons());
+                assert.ok(query('#sendbutton').classList.contains('hidden'));
+                assert.ok(query('#burnafterreadingoption').classList.contains('hidden'));
+                cleanup();
+            }
+        );
     });
     });
 
 
     describe('send shortcut', function () {
     describe('send shortcut', function () {
@@ -566,6 +591,21 @@ describe('TopNav', function () {
                 assert.ok(result);
                 assert.ok(result);
             }
             }
         );
         );
+
+        it(
+            'tolerates a missing discussion option (discussion disabled)',
+            function () {
+                document.documentElement.innerHTML =
+                    '<nav><div id="navbar"><ul><li id="burnafterreadingoption" ' +
+                    'class="buttondisabled"><label><input type="checkbox" ' +
+                    'id="burnafterreading" name="burnafterreading" /> ' +
+                    'Burn after reading</label></li></ul></div></nav>';
+                PrivateBin.TopNav.init();
+                assert.doesNotThrow(() => PrivateBin.TopNav.resetInput());
+                assert.ok(!query('#burnafterreadingoption').classList.contains('buttondisabled'));
+                cleanup();
+            }
+        );
     });
     });
 
 
     describe('getExpiration', function () {
     describe('getExpiration', function () {

+ 1 - 1
lib/Configuration.php

@@ -124,7 +124,7 @@ class Configuration
             'js/kjua-0.10.0.js'      => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
             'js/kjua-0.10.0.js'      => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
             'js/legacy.js'           => 'sha512-pRofxsrf5UItjiP22Dcjh3FAcBjF/n7h8U9/W5xqJk17U0N2U1oajhXypq/omo9jhwS1iVGOhWrRepoPeFns+w==',
             'js/legacy.js'           => 'sha512-pRofxsrf5UItjiP22Dcjh3FAcBjF/n7h8U9/W5xqJk17U0N2U1oajhXypq/omo9jhwS1iVGOhWrRepoPeFns+w==',
             'js/prettify.js'         => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
             'js/prettify.js'         => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
-            'js/privatebin.js'       => 'sha512-ECxau81icFK+pB7VsyYoxEMXrERcuE7ILhtTD6dedfsHZfL59+4f57SSvvpwIy/CZ665jyvtYZ9FBDZ1Lx3DAQ==',
+            'js/privatebin.js'       => 'sha512-EnB+zyT/Hl7nRvSdO3htInodeShz5dNCKzyRkqVqNdqzKMbNCegubjmfBHlflDT4DbhYVsqOMJ8G/Eaty9wosA==',
             'js/purify-3.4.17.js'    => 'sha512-udF2zPdEyVCAxIGeBbeSGFK1Fj5c4eHGi6NqOSsr8M75lGs39POzgUNxO3SFxOg4cHfAoZJgR2EaXdmDi6v6AQ==',
             'js/purify-3.4.17.js'    => 'sha512-udF2zPdEyVCAxIGeBbeSGFK1Fj5c4eHGi6NqOSsr8M75lGs39POzgUNxO3SFxOg4cHfAoZJgR2EaXdmDi6v6AQ==',
             'js/showdown-2.1.0.js'   => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
             'js/showdown-2.1.0.js'   => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
             'js/zlib-1.3.2.js'       => 'sha512-RAhJgxg9siMIA8ky4c10Rc2zUgnK80olHB8Tt1IOYWY4Eh1WmrviQkDn+sgBlb38ZHq3tzufGC41kP360gmosQ==',
             'js/zlib-1.3.2.js'       => 'sha512-RAhJgxg9siMIA8ky4c10Rc2zUgnK80olHB8Tt1IOYWY4Eh1WmrviQkDn+sgBlb38ZHq3tzufGC41kP360gmosQ==',

+ 1 - 1
lib/I18n.php

@@ -411,7 +411,7 @@ class I18n
             case 'th':
             case 'th':
                 return 0;
                 return 0;
             case 'lt':
             case 'lt':
-                return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
+                return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20)) ? 1 : 2);
             case 'pl':
             case 'pl':
                 return $n === 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
                 return $n === 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
             case 'ro':
             case 'ro':

+ 1 - 1
tpl/bootstrap5.php

@@ -449,7 +449,7 @@ if (!empty($URLSHORTENER)) :
 								<?php if ($SHORTENBYDEFAULT) : ?>
 								<?php if ($SHORTENBYDEFAULT) : ?>
 								data-autoshorten="true"
 								data-autoshorten="true"
 								<?php endif; ?>
 								<?php endif; ?>
-								type="button" class="btn btn-primary btn-block d-flex justify-content-center align-items-center gap-1"
+								type="button" class="btn btn-primary btn-block d-flex justify-content-center align-items-center gap-1<?php echo $SHORTENBYDEFAULT ? ' hidden' : ''; ?>"
 						>
 						>
 							<svg width="16" height="16" fill="currentColor" aria-hidden="true"><use href="img/bootstrap-icons.svg#send" /></svg> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
 							<svg width="16" height="16" fill="currentColor" aria-hidden="true"><use href="img/bootstrap-icons.svg#send" /></svg> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
 						</button>
 						</button>

+ 22 - 8
tst/I18nTest.php

@@ -95,6 +95,20 @@ class I18nTest extends TestCase
         );
         );
     }
     }
 
 
+    public function testBrowserLanguageLtDetection()
+    {
+        $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'lt;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
+        I18n::loadTranslations();
+        $this->assertEquals('lt', I18n::getLanguage(), 'browser language lt');
+        $this->assertEquals('1 sekundę', I18n::_('%d seconds', 1), '1 second in Lithuanian');
+        $this->assertEquals('2 sekundės', I18n::_('%d seconds', 2), '2 seconds in Lithuanian');
+        $this->assertEquals('10 sekundžių', I18n::_('%d seconds', 10), '10 seconds in Lithuanian');
+        $this->assertEquals('22 sekundės', I18n::_('%d seconds', 22), '22 seconds in Lithuanian');
+        $this->assertEquals('110 sekundžių', I18n::_('%d seconds', 110), '110 seconds in Lithuanian');
+        $this->assertEquals('111 sekundžių', I18n::_('%d seconds', 111), '111 seconds in Lithuanian');
+        $this->assertEquals('120 sekundžių', I18n::_('%d seconds', 120), '120 seconds in Lithuanian');
+    }
+
     public function testBrowserLanguageNoDetection()
     public function testBrowserLanguageNoDetection()
     {
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'no;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'no;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
@@ -177,14 +191,14 @@ class I18nTest extends TestCase
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'sl;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'sl;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
         I18n::loadTranslations();
         $this->assertEquals('sl', I18n::getLanguage(), 'browser language sl');
         $this->assertEquals('sl', I18n::getLanguage(), 'browser language sl');
-        $this->assertEquals('0 ura',  I18n::_('%d hours', 0), '0 hours in Slovene');
-        $this->assertEquals('1 uri',  I18n::_('%d hours', 1), '1 hour in Slovene');
-        $this->assertEquals('2 ure', I18n::_('%d hours', 2), '2 hours in Slovene');
-        $this->assertEquals('3 ur',  I18n::_('%d hours', 3), '3 hours in Slovene');
-        $this->assertEquals('11 ura',  I18n::_('%d hours', 11), '11 hours in Slovene');
-        $this->assertEquals('101 uri',  I18n::_('%d hours', 101), '101 hours in Slovene');
-        $this->assertEquals('102 ure', I18n::_('%d hours', 102), '102 hours in Slovene');
-        $this->assertEquals('104 ur',  I18n::_('%d hours', 104), '104 hours in Slovene');
+        $this->assertEquals('0 ur',  I18n::_('%d hours', 0), '0 hours in Slovene');
+        $this->assertEquals('1 ura',  I18n::_('%d hours', 1), '1 hour in Slovene');
+        $this->assertEquals('2 uri', I18n::_('%d hours', 2), '2 hours in Slovene');
+        $this->assertEquals('3 ure',  I18n::_('%d hours', 3), '3 hours in Slovene');
+        $this->assertEquals('11 ur',  I18n::_('%d hours', 11), '11 hours in Slovene');
+        $this->assertEquals('101 ura',  I18n::_('%d hours', 101), '101 hours in Slovene');
+        $this->assertEquals('102 uri', I18n::_('%d hours', 102), '102 hours in Slovene');
+        $this->assertEquals('104 ure',  I18n::_('%d hours', 104), '104 hours in Slovene');
     }
     }
 
 
     public function testBrowserLanguageCsDetection()
     public function testBrowserLanguageCsDetection()