diff options
Diffstat (limited to 'apps')
177 files changed, 2546 insertions, 235 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 9021ba98a0f..baf1021d3e6 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -10,7 +10,7 @@ <name>WebDAV</name> <summary>WebDAV endpoint</summary> <description>WebDAV endpoint</description> - <version>1.34.0</version> + <version>1.34.1</version> <licence>agpl</licence> <author>owncloud.org</author> <namespace>DAV</namespace> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 764f94ef665..9eab0456159 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -216,6 +216,8 @@ return array( 'OCA\\DAV\\Connector\\Sabre\\Node' => $baseDir . '/../lib/Connector/Sabre/Node.php', 'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => $baseDir . '/../lib/Connector/Sabre/ObjectTree.php', 'OCA\\DAV\\Connector\\Sabre\\Principal' => $baseDir . '/../lib/Connector/Sabre/Principal.php', + 'OCA\\DAV\\Connector\\Sabre\\PropFindMonitorPlugin' => $baseDir . '/../lib/Connector/Sabre/PropFindMonitorPlugin.php', + 'OCA\\DAV\\Connector\\Sabre\\PropFindPreloadNotifyPlugin' => $baseDir . '/../lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => $baseDir . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => $baseDir . '/../lib/Connector/Sabre/PublicAuth.php', 'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => $baseDir . '/../lib/Connector/Sabre/QuotaPlugin.php', @@ -353,6 +355,7 @@ return array( 'OCA\\DAV\\Migration\\Version1029Date20231004091403' => $baseDir . '/../lib/Migration/Version1029Date20231004091403.php', 'OCA\\DAV\\Migration\\Version1030Date20240205103243' => $baseDir . '/../lib/Migration/Version1030Date20240205103243.php', 'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php', + 'OCA\\DAV\\Migration\\Version1034Date20250813093701' => $baseDir . '/../lib/Migration/Version1034Date20250813093701.php', 'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php', 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php', 'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index f3d1eacfcd0..e9a0ef01c07 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -231,6 +231,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Connector\\Sabre\\Node' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Node.php', 'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ObjectTree.php', 'OCA\\DAV\\Connector\\Sabre\\Principal' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Principal.php', + 'OCA\\DAV\\Connector\\Sabre\\PropFindMonitorPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropFindMonitorPlugin.php', + 'OCA\\DAV\\Connector\\Sabre\\PropFindPreloadNotifyPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PublicAuth.php', 'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/QuotaPlugin.php', @@ -368,6 +370,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1029Date20231004091403' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20231004091403.php', 'OCA\\DAV\\Migration\\Version1030Date20240205103243' => __DIR__ . '/..' . '/../lib/Migration/Version1030Date20240205103243.php', 'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php', + 'OCA\\DAV\\Migration\\Version1034Date20250813093701' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250813093701.php', 'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php', 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php', 'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php', diff --git a/apps/dav/l10n/pl.js b/apps/dav/l10n/pl.js index 3c9929cc943..8d72b6d7632 100644 --- a/apps/dav/l10n/pl.js +++ b/apps/dav/l10n/pl.js @@ -79,6 +79,9 @@ OC.L10N.register( "_In a week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za tydzień, dnia %1$s, przez cały dzień","Za %n tygodnie, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień"], "_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["W ciągu miesiąca, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień"], "_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za rok dnia %1$s przez cały dzień","Za %n lata dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień"], + "In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s", + "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za minutę dnia %1$s między %2$s - %3$s","Za %n minuty dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s"], + "_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Za godzinę dnia %1$s między %2$s - %3$s","Za %n godziny dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s"], "_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Za dzień, dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s"], "_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Za tydzień dnia %1$s między %2$s - %3$s","Za %n tygodnie dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s"], "_In a month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Za miesiąc dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s"], @@ -87,18 +90,38 @@ OC.L10N.register( "Every Day for the entire day" : "Codziennie przez cały dzień", "Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s", "Every Day between %1$s - %2$s" : "Codziennie między %1$s – %2$s", + "Every Day between %1$s - %2$s until %3$s" : "Codziennie między %1$s - %2$s do %3$s", "Every %1$d Days for the entire day" : "Co %1$d dni przez cały dzień", "Every %1$d Days for the entire day until %2$s" : "Co %1$d dni przez cały dzień aż do %2$s", "Every %1$d Days between %2$s - %3$s" : "Co %1$d dni pomiędzy %2$s - %3$s", "Every %1$d Days between %2$s - %3$s until %4$s" : "Co %1$d dni, pomiędzy %2$s - %3$s aż do %4$s", "Could not generate event recurrence statement" : "Nie można wygenerować zestawienia powtórzeń zdarzenia", "Every Week on %1$s for the entire day" : "Każdego tygodnia w %1$s przez cały dzień", + "Every Week on %1$s for the entire day until %2$s" : "Co tydzień w %1$s przez cały dzień do %2$s", + "Every Week on %1$s between %2$s - %3$s" : "Co tydzień w %1$s między %2$s - %3$s", + "Every Week on %1$s between %2$s - %3$s until %4$s" : "Co tydzień w %1$s między %2$s - %3$s do %4$s", + "Every %1$d Weeks on %2$s for the entire day" : "Co %1$d tygodni w %2$s przez cały dzień", + "Every %1$d Weeks on %2$s for the entire day until %3$s" : "Co %1$d tygodnie w %2$s przez cały dzień do %3$s", + "Every %1$d Weeks on %2$s between %3$s - %4$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s", + "Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s do %5$s", + "Every Month on the %1$s for the entire day" : "Co miesiąc dnia %1$s przez cały dzień", + "Every Month on the %1$s for the entire day until %2$s" : "Co miesiąc dnia %1$s przez cały dzień do %2$s", + "Every Month on the %1$s between %2$s - %3$s" : "Co miesiąc dnia %1$s między %2$s - %3$s", + "Every Month on the %1$s between %2$s - %3$s until %4$s" : "Co miesiąc dnia %1$s między %2$s - %3$s do %4$s", + "Every %1$d Months on the %2$s for the entire day" : "Co %1$d miesiący dnia %2$s przez cały dzień", + "Every %1$d Months on the %2$s for the entire day until %3$s" : "Co %1$d miesięcy dnia %2$s przez cały dzień do %3$s", + "Every %1$d Months on the %2$s between %3$s - %4$s" : "Co %1$d miesięcy dnia %2$s między %3$s - %4$s", "Every %1$d Months on the %2$s between %3$s - %4$s until %5$s" : "Każdego %1$d miesiąca dnia %2$s między %3$s - %4$s do %5$s", "Every Year in %1$s on the %2$s for the entire day" : "Co rok w %1$s dnia %2$s przez cały dzień", "Every Year in %1$s on the %2$s for the entire day until %3$s" : "Co rok w %1$s dnia %2$s przez cały dzień do %3$s", "Every Year in %1$s on the %2$s between %3$s - %4$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s", "Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s do %5$s", "Every %1$d Years in %2$s on the %3$s for the entire day" : "Co %1$d lat dnia %2$s o %3$s przez cały dzień", + "Every %1$d Years in %2$s on the %3$s for the entire day until %4$s" : "Co %1$d lat w %2$s dnia %3$s przez cały dzień do %4$s", + "Every %1$d Years in %2$s on the %3$s between %4$s - %5$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s", + "Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s do %6$s", + "On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s", + "On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s", "In the past on %1$s" : "W przeszłości dnia %1$s", "_In a minute on %1$s_::_In %n minutes on %1$s_" : ["Za minutę dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s"], "_In a hour on %1$s_::_In %n hours on %1$s_" : ["Za godzinę dnia %1$s","Za %n godziny dnia %1$s","Za %n godzin dnia %1$s","Za %n godzin dnia %1$s"], @@ -108,6 +131,18 @@ OC.L10N.register( "_In a year on %1$s_::_In %n years on %1$s_" : ["Za rok dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s"], "In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s", "_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za minutę dnia %1$s, a następnie %2$s","Za %n minuty dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s"], + "_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s","Za %n godziny dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s"], + "_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Jutro dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s"], + "_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s"], + "_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s","Za %n miesiące dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s"], + "_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za rok dnia %1$s, następnie dnia %2$s","Za %n lata dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s"], + "In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s", + "_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Za minutę dnia %1$s, następnie dnia %2$s i %3$s","Za %n minuty dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s i %3$s","Za %%ngodziny dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Jutro, dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesiące dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Za rok dnia %1$s, następnie dnia %2$s i %3$s","Za %n lata dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s"], "Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania", "Cancelled: %1$s" : "Anulowane: %1$s", "\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane", diff --git a/apps/dav/l10n/pl.json b/apps/dav/l10n/pl.json index 4b2ffc40dc0..242c36b857a 100644 --- a/apps/dav/l10n/pl.json +++ b/apps/dav/l10n/pl.json @@ -77,6 +77,9 @@ "_In a week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za tydzień, dnia %1$s, przez cały dzień","Za %n tygodnie, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień"], "_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["W ciągu miesiąca, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień"], "_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za rok dnia %1$s przez cały dzień","Za %n lata dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień"], + "In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s", + "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za minutę dnia %1$s między %2$s - %3$s","Za %n minuty dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s"], + "_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Za godzinę dnia %1$s między %2$s - %3$s","Za %n godziny dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s"], "_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Za dzień, dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s"], "_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Za tydzień dnia %1$s między %2$s - %3$s","Za %n tygodnie dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s"], "_In a month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Za miesiąc dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s"], @@ -85,18 +88,38 @@ "Every Day for the entire day" : "Codziennie przez cały dzień", "Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s", "Every Day between %1$s - %2$s" : "Codziennie między %1$s – %2$s", + "Every Day between %1$s - %2$s until %3$s" : "Codziennie między %1$s - %2$s do %3$s", "Every %1$d Days for the entire day" : "Co %1$d dni przez cały dzień", "Every %1$d Days for the entire day until %2$s" : "Co %1$d dni przez cały dzień aż do %2$s", "Every %1$d Days between %2$s - %3$s" : "Co %1$d dni pomiędzy %2$s - %3$s", "Every %1$d Days between %2$s - %3$s until %4$s" : "Co %1$d dni, pomiędzy %2$s - %3$s aż do %4$s", "Could not generate event recurrence statement" : "Nie można wygenerować zestawienia powtórzeń zdarzenia", "Every Week on %1$s for the entire day" : "Każdego tygodnia w %1$s przez cały dzień", + "Every Week on %1$s for the entire day until %2$s" : "Co tydzień w %1$s przez cały dzień do %2$s", + "Every Week on %1$s between %2$s - %3$s" : "Co tydzień w %1$s między %2$s - %3$s", + "Every Week on %1$s between %2$s - %3$s until %4$s" : "Co tydzień w %1$s między %2$s - %3$s do %4$s", + "Every %1$d Weeks on %2$s for the entire day" : "Co %1$d tygodni w %2$s przez cały dzień", + "Every %1$d Weeks on %2$s for the entire day until %3$s" : "Co %1$d tygodnie w %2$s przez cały dzień do %3$s", + "Every %1$d Weeks on %2$s between %3$s - %4$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s", + "Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s do %5$s", + "Every Month on the %1$s for the entire day" : "Co miesiąc dnia %1$s przez cały dzień", + "Every Month on the %1$s for the entire day until %2$s" : "Co miesiąc dnia %1$s przez cały dzień do %2$s", + "Every Month on the %1$s between %2$s - %3$s" : "Co miesiąc dnia %1$s między %2$s - %3$s", + "Every Month on the %1$s between %2$s - %3$s until %4$s" : "Co miesiąc dnia %1$s między %2$s - %3$s do %4$s", + "Every %1$d Months on the %2$s for the entire day" : "Co %1$d miesiący dnia %2$s przez cały dzień", + "Every %1$d Months on the %2$s for the entire day until %3$s" : "Co %1$d miesięcy dnia %2$s przez cały dzień do %3$s", + "Every %1$d Months on the %2$s between %3$s - %4$s" : "Co %1$d miesięcy dnia %2$s między %3$s - %4$s", "Every %1$d Months on the %2$s between %3$s - %4$s until %5$s" : "Każdego %1$d miesiąca dnia %2$s między %3$s - %4$s do %5$s", "Every Year in %1$s on the %2$s for the entire day" : "Co rok w %1$s dnia %2$s przez cały dzień", "Every Year in %1$s on the %2$s for the entire day until %3$s" : "Co rok w %1$s dnia %2$s przez cały dzień do %3$s", "Every Year in %1$s on the %2$s between %3$s - %4$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s", "Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s do %5$s", "Every %1$d Years in %2$s on the %3$s for the entire day" : "Co %1$d lat dnia %2$s o %3$s przez cały dzień", + "Every %1$d Years in %2$s on the %3$s for the entire day until %4$s" : "Co %1$d lat w %2$s dnia %3$s przez cały dzień do %4$s", + "Every %1$d Years in %2$s on the %3$s between %4$s - %5$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s", + "Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s do %6$s", + "On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s", + "On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s", "In the past on %1$s" : "W przeszłości dnia %1$s", "_In a minute on %1$s_::_In %n minutes on %1$s_" : ["Za minutę dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s"], "_In a hour on %1$s_::_In %n hours on %1$s_" : ["Za godzinę dnia %1$s","Za %n godziny dnia %1$s","Za %n godzin dnia %1$s","Za %n godzin dnia %1$s"], @@ -106,6 +129,18 @@ "_In a year on %1$s_::_In %n years on %1$s_" : ["Za rok dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s"], "In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s", "_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za minutę dnia %1$s, a następnie %2$s","Za %n minuty dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s"], + "_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s","Za %n godziny dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s"], + "_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Jutro dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s"], + "_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s"], + "_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s","Za %n miesiące dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s"], + "_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za rok dnia %1$s, następnie dnia %2$s","Za %n lata dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s"], + "In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s", + "_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Za minutę dnia %1$s, następnie dnia %2$s i %3$s","Za %n minuty dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s i %3$s","Za %%ngodziny dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Jutro, dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesiące dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s"], + "_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Za rok dnia %1$s, następnie dnia %2$s i %3$s","Za %n lata dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s"], "Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania", "Cancelled: %1$s" : "Anulowane: %1$s", "\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane", diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index dd3a4cf3f69..deb00caa93d 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -36,7 +36,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable public function __construct( BackendInterface $caldavBackend, - $calendarInfo, + array $calendarInfo, IL10N $l10n, private IConfig $config, private LoggerInterface $logger, @@ -60,6 +60,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable $this->l10n = $l10n; } + public function getUri(): string { + return $this->calendarInfo['uri']; + } + /** * {@inheritdoc} * @throws Forbidden diff --git a/apps/dav/lib/CalDAV/CalendarProvider.php b/apps/dav/lib/CalDAV/CalendarProvider.php index 3cc4039ed36..a8b818e59aa 100644 --- a/apps/dav/lib/CalDAV/CalendarProvider.php +++ b/apps/dav/lib/CalDAV/CalendarProvider.php @@ -36,9 +36,14 @@ class CalendarProvider implements ICalendarProvider { }); } + $additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos); $iCalendars = []; foreach ($calendarInfos as $calendarInfo) { - $calendarInfo = array_merge($calendarInfo, $this->getAdditionalProperties($calendarInfo['principaluri'], $calendarInfo['uri'])); + $user = str_replace('principals/users/', '', $calendarInfo['principaluri']); + $path = 'calendars/' . $user . '/' . $calendarInfo['uri']; + + $calendarInfo = array_merge($calendarInfo, $additionalProperties[$path] ?? []); + $calendar = new Calendar($this->calDavBackend, $calendarInfo, $this->l10n, $this->config, $this->logger); $iCalendars[] = new CalendarImpl( $calendar, @@ -49,16 +54,34 @@ class CalendarProvider implements ICalendarProvider { return $iCalendars; } - public function getAdditionalProperties(string $principalUri, string $calendarUri): array { - $user = str_replace('principals/users/', '', $principalUri); - $path = 'calendars/' . $user . '/' . $calendarUri; + /** + * @param array{ + * principaluri: string, + * uri: string, + * }[] $uris + * @return array<string, array<string, string|bool>> + */ + private function getAdditionalPropertiesForCalendars(array $uris): array { + $calendars = []; + foreach ($uris as $uri) { + /** @var string $user */ + $user = str_replace('principals/users/', '', $uri['principaluri']); + if (!array_key_exists($user, $calendars)) { + $calendars[$user] = []; + } + $calendars[$user][] = 'calendars/' . $user . '/' . $uri['uri']; + } - $properties = $this->propertyMapper->findPropertiesByPath($user, $path); + $properties = $this->propertyMapper->findPropertiesByPathsAndUsers($calendars); $list = []; foreach ($properties as $property) { if ($property instanceof Property) { - $list[$property->getPropertyname()] = match ($property->getPropertyname()) { + if (!isset($list[$property->getPropertypath()])) { + $list[$property->getPropertypath()] = []; + } + + $list[$property->getPropertypath()][$property->getPropertyname()] = match ($property->getPropertyname()) { '{http://owncloud.org/ns}calendar-enabled' => (bool)$property->getPropertyvalue(), default => $property->getPropertyvalue() }; diff --git a/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php b/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php index 21d8c06fa99..d9d6d840c5e 100644 --- a/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php +++ b/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php @@ -20,6 +20,7 @@ use OCA\DAV\Connector\Sabre\DavAclPlugin; use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; use OCA\DAV\Connector\Sabre\LockPlugin; use OCA\DAV\Connector\Sabre\MaintenancePlugin; +use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin; use OCA\DAV\Events\SabrePluginAuthInitEvent; use OCA\DAV\RootCollection; use OCA\Theming\ThemingDefaults; @@ -96,6 +97,9 @@ class EmbeddedCalDavServer { $this->server->addPlugin(Server::get(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class)); } + // collection preload plugin + $this->server->addPlugin(new PropFindPreloadNotifyPlugin()); + // wait with registering these until auth is handled and the filesystem is setup $this->server->on('beforeMethod:*', function () use ($root): void { // register plugins from apps diff --git a/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php b/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php index e4b6c2636da..ef9bd1ae472 100644 --- a/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php @@ -10,6 +10,7 @@ namespace OCA\DAV\Connector\Sabre; use OCP\Comments\ICommentsManager; use OCP\IUserSession; +use Sabre\DAV\ICollection; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; @@ -21,6 +22,7 @@ class CommentPropertiesPlugin extends ServerPlugin { protected ?Server $server = null; private array $cachedUnreadCount = []; + private array $cachedDirectories = []; public function __construct( private ICommentsManager $commentsManager, @@ -41,6 +43,8 @@ class CommentPropertiesPlugin extends ServerPlugin { */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; + + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'handleGetProperties']); } @@ -69,6 +73,21 @@ class CommentPropertiesPlugin extends ServerPlugin { } } + private function preloadCollection(PropFind $propFind, ICollection $collection): + void { + if (!($collection instanceof Directory)) { + return; + } + + $collectionPath = $collection->getPath(); + if (!isset($this->cachedDirectories[$collectionPath]) && $propFind->getStatus( + self::PROPERTY_NAME_UNREAD + ) !== null) { + $this->cacheDirectory($collection); + $this->cachedDirectories[$collectionPath] = true; + } + } + /** * Adds tags and favorites properties to the response, * if requested. @@ -85,14 +104,6 @@ class CommentPropertiesPlugin extends ServerPlugin { return; } - // need prefetch ? - if ($node instanceof Directory - && $propFind->getDepth() !== 0 - && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD)) - ) { - $this->cacheDirectory($node); - } - $propFind->handle(self::PROPERTY_NAME_COUNT, function () use ($node): int { return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId()); }); diff --git a/apps/dav/lib/Connector/Sabre/PropFindMonitorPlugin.php b/apps/dav/lib/Connector/Sabre/PropFindMonitorPlugin.php new file mode 100644 index 00000000000..38538fdcff0 --- /dev/null +++ b/apps/dav/lib/Connector/Sabre/PropFindMonitorPlugin.php @@ -0,0 +1,82 @@ +<?php + +declare(strict_types = 1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Connector\Sabre; + +use Sabre\DAV\Server as SabreServer; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * This plugin runs after requests and logs an error if a plugin is detected + * to be doing too many SQL requests. + */ +class PropFindMonitorPlugin extends ServerPlugin { + + /** + * A Plugin can scan up to this amount of nodes without an error being + * reported. + */ + public const THRESHOLD_NODES = 50; + + /** + * A plugin can use up to this amount of queries per node. + */ + public const THRESHOLD_QUERY_FACTOR = 1; + + private SabreServer $server; + + public function initialize(SabreServer $server): void { + $this->server = $server; + $this->server->on('afterResponse', [$this, 'afterResponse']); + } + + public function afterResponse( + RequestInterface $request, + ResponseInterface $response): void { + if (!$this->server instanceof Server) { + return; + } + + $pluginQueries = $this->server->getPluginQueries(); + if (empty($pluginQueries)) { + return; + } + + $logger = $this->server->getLogger(); + foreach ($pluginQueries as $eventName => $eventQueries) { + $maxDepth = max(0, ...array_keys($eventQueries)); + // entries at the top are usually not interesting + unset($eventQueries[$maxDepth]); + foreach ($eventQueries as $depth => $propFinds) { + foreach ($propFinds as $pluginName => $propFind) { + [ + 'queries' => $queries, + 'nodes' => $nodes + ] = $propFind; + if ($queries === 0 || $nodes > $queries || $nodes < self::THRESHOLD_NODES + || $queries < $nodes * self::THRESHOLD_QUERY_FACTOR) { + continue; + } + $logger->error( + '{name}:{event} scanned {scans} nodes with {count} queries in depth {depth}/{maxDepth}. This is bad for performance, please report to the plugin developer!', + [ + 'name' => $pluginName, + 'scans' => $nodes, + 'count' => $queries, + 'depth' => $depth, + 'maxDepth' => $maxDepth, + 'event' => $eventName, + ] + ); + } + } + } + } +} diff --git a/apps/dav/lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php b/apps/dav/lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php new file mode 100644 index 00000000000..c7b0c64132c --- /dev/null +++ b/apps/dav/lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types = 1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Connector\Sabre; + +use Sabre\DAV\ICollection; +use Sabre\DAV\INode; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; + +/** + * This plugin asks other plugins to preload data for a collection, so that + * subsequent PROPFIND handlers for children do not query the DB on a per-node + * basis. + */ +class PropFindPreloadNotifyPlugin extends ServerPlugin { + + private Server $server; + + public function initialize(Server $server): void { + $this->server = $server; + $this->server->on('propFind', [$this, 'collectionPreloadNotifier' ], 1); + } + + /** + * Uses the server instance to emit a `preloadCollection` event to signal + * to interested plugins that a collection can be preloaded. + * + * NOTE: this can be emitted several times, so ideally every plugin + * should cache what they need and check if a cache exists before + * re-fetching. + */ + public function collectionPreloadNotifier(PropFind $propFind, INode $node): bool { + if (!$this->shouldPreload($propFind, $node)) { + return true; + } + + return $this->server->emit('preloadCollection', [$propFind, $node]); + } + + private function shouldPreload( + PropFind $propFind, + INode $node, + ): bool { + $depth = $propFind->getDepth(); + return $node instanceof ICollection + && ($depth === Server::DEPTH_INFINITY || $depth > 0); + } +} diff --git a/apps/dav/lib/Connector/Sabre/Server.php b/apps/dav/lib/Connector/Sabre/Server.php index f3bfac1d6e0..eef65000131 100644 --- a/apps/dav/lib/Connector/Sabre/Server.php +++ b/apps/dav/lib/Connector/Sabre/Server.php @@ -7,7 +7,11 @@ */ namespace OCA\DAV\Connector\Sabre; +use OC\DB\Connection; +use Override; use Sabre\DAV\Exception; +use Sabre\DAV\INode; +use Sabre\DAV\PropFind; use Sabre\DAV\Version; use TypeError; @@ -22,6 +26,15 @@ class Server extends \Sabre\DAV\Server { /** @var CachingTree $tree */ /** + * Tracks queries done by plugins. + * @var array<string, array<int, array<string, array{nodes:int, + * queries:int}>>> The keys represent: event name, depth and plugin name + */ + private array $pluginQueries = []; + + public bool $debugEnabled = false; + + /** * @see \Sabre\DAV\Server */ public function __construct($treeOrNode = null) { @@ -30,6 +43,106 @@ class Server extends \Sabre\DAV\Server { $this->enablePropfindDepthInfinity = true; } + #[Override] + public function once( + string $eventName, + callable $callBack, + int $priority = 100, + ): void { + $this->debugEnabled ? $this->monitorPropfindQueries( + parent::once(...), + ...\func_get_args(), + ) : parent::once(...\func_get_args()); + } + + #[Override] + public function on( + string $eventName, + callable $callBack, + int $priority = 100, + ): void { + $this->debugEnabled ? $this->monitorPropfindQueries( + parent::on(...), + ...\func_get_args(), + ) : parent::on(...\func_get_args()); + } + + /** + * Wraps the handler $callBack into a query-monitoring function and calls + * $parentFn to register it. + */ + private function monitorPropfindQueries( + callable $parentFn, + string $eventName, + callable $callBack, + int $priority = 100, + ): void { + $pluginName = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['class'] ?? 'unknown'; + // The NotifyPlugin needs to be excluded as it emits the + // `preloadCollection` event, which causes many plugins run queries. + /** @psalm-suppress TypeDoesNotContainType */ + if ($pluginName === PropFindPreloadNotifyPlugin::class || ($eventName !== 'propFind' + && $eventName !== 'preloadCollection')) { + $parentFn($eventName, $callBack, $priority); + return; + } + + $callback = $this->getMonitoredCallback($callBack, $pluginName, $eventName); + + $parentFn($eventName, $callback, $priority); + } + + /** + * Returns a callable that wraps $callBack with code that monitors and + * records queries per plugin. + */ + private function getMonitoredCallback( + callable $callBack, + string $pluginName, + string $eventName, + ): callable { + return function (PropFind $propFind, INode $node) use ( + $callBack, + $pluginName, + $eventName, + ): bool { + $connection = \OCP\Server::get(Connection::class); + $queriesBefore = $connection->getStats()['executed']; + $result = $callBack($propFind, $node); + $queriesAfter = $connection->getStats()['executed']; + $this->trackPluginQueries( + $pluginName, + $eventName, + $queriesAfter - $queriesBefore, + $propFind->getDepth() + ); + + // many callbacks don't care about returning a bool + return $result ?? true; + }; + } + + /** + * Tracks the queries executed by a specific plugin. + */ + private function trackPluginQueries( + string $pluginName, + string $eventName, + int $queriesExecuted, + int $depth, + ): void { + // report only nodes which cause queries to the DB + if ($queriesExecuted === 0) { + return; + } + + $this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] + = ($this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] ?? 0) + 1; + + $this->pluginQueries[$eventName][$depth][$pluginName]['queries'] + = ($this->pluginQueries[$eventName][$depth][$pluginName]['queries'] ?? 0) + $queriesExecuted; + } + /** * * @return void @@ -115,4 +228,13 @@ class Server extends \Sabre\DAV\Server { $this->sapi->sendResponse($this->httpResponse); } } + + /** + * Returns queries executed by registered plugins. + * @return array<string, array<int, array<string, array{nodes:int, + * queries:int}>>> The keys represent: event name, depth and plugin name + */ + public function getPluginQueries(): array { + return $this->pluginQueries; + } } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 3749b506d16..1b4de841ec6 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -14,6 +14,7 @@ use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\DAV\CustomPropertiesBackend; use OCA\DAV\DAV\ViewOnlyPlugin; +use OCA\DAV\Db\PropertyMapper; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\Sharing\RootCollection; use OCA\DAV\Upload\CleanupService; @@ -68,6 +69,7 @@ class ServerFactory { Plugin $authPlugin, callable $viewCallBack, ): Server { + $debugEnabled = $this->config->getSystemValue('debug', false); // Fire up server if ($isPublicShare) { $rootCollection = new SimpleCollection('root'); @@ -89,6 +91,12 @@ class ServerFactory { )); $server->addPlugin(new AnonymousOptionsPlugin()); $server->addPlugin($authPlugin); + if ($debugEnabled) { + $server->debugEnabled = $debugEnabled; + $server->addPlugin(new PropFindMonitorPlugin()); + } + + $server->addPlugin(new PropFindPreloadNotifyPlugin()); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new DummyGetResponsePlugin()); $server->addPlugin(new ExceptionLoggerPlugin('webdav', $this->logger)); @@ -117,7 +125,8 @@ class ServerFactory { } // wait with registering these until auth is handled and the filesystem is setup - $server->on('beforeMethod:*', function () use ($server, $tree, $viewCallBack, $isPublicShare, $rootCollection): void { + $server->on('beforeMethod:*', function () use ($server, $tree, + $viewCallBack, $isPublicShare, $rootCollection, $debugEnabled): void { // ensure the skeleton is copied $userFolder = \OC::$server->getUserFolder(); @@ -181,7 +190,7 @@ class ServerFactory { \OCP\Server::get(IFilenameValidator::class), \OCP\Server::get(IAccountManager::class), false, - !$this->config->getSystemValue('debug', false) + !$debugEnabled ) ); $server->addPlugin(new QuotaPlugin($view)); @@ -220,6 +229,7 @@ class ServerFactory { $tree, $this->databaseConnection, $this->userSession->getUser(), + \OCP\Server::get(PropertyMapper::class), \OCP\Server::get(DefaultCalendarValidator::class), ) ) diff --git a/apps/dav/lib/Connector/Sabre/SharesPlugin.php b/apps/dav/lib/Connector/Sabre/SharesPlugin.php index f49e85333f3..11e50362dc2 100644 --- a/apps/dav/lib/Connector/Sabre/SharesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/SharesPlugin.php @@ -15,6 +15,7 @@ use OCP\Files\NotFoundException; use OCP\IUserSession; use OCP\Share\IManager; use OCP\Share\IShare; +use Sabre\DAV\ICollection; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Tree; @@ -38,7 +39,14 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { /** @var IShare[][] */ private array $cachedShares = []; - /** @var string[] */ + + /** + * Tracks which folders have been cached. + * When a folder is cached, it will appear with its path as key and true + * as value. + * + * @var bool[] + */ private array $cachedFolders = []; public function __construct( @@ -67,6 +75,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { $server->protectedProperties[] = self::SHAREES_PROPERTYNAME; $this->server = $server; + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'handleGetProperties']); } @@ -89,28 +98,28 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { ]; foreach ($requestedShareTypes as $requestedShareType) { - $result = array_merge($result, $this->shareManager->getSharesBy( + $result[] = $this->shareManager->getSharesBy( $this->userId, $requestedShareType, $node, false, -1 - )); + ); // Also check for shares where the user is the recipient try { - $result = array_merge($result, $this->shareManager->getSharedWith( + $result[] = $this->shareManager->getSharedWith( $this->userId, $requestedShareType, $node, -1 - )); + ); } catch (BackendError $e) { // ignore } } - return $result; + return array_merge(...$result); } /** @@ -141,7 +150,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { // if we already cached the folder containing this file // then we already know there are no shares here. - if (array_search($parentPath, $this->cachedFolders) === false) { + if (!isset($this->cachedFolders[$parentPath])) { try { $node = $sabreNode->getNode(); } catch (NotFoundException $e) { @@ -156,6 +165,27 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { return []; } + private function preloadCollection(PropFind $propFind, ICollection $collection): void { + if (!$collection instanceof Directory + || isset($this->cachedFolders[$collection->getPath()]) + || ( + $propFind->getStatus(self::SHARETYPES_PROPERTYNAME) === null + && $propFind->getStatus(self::SHAREES_PROPERTYNAME) === null + ) + ) { + return; + } + + // If the node is a directory and we are requesting share types or sharees + // then we get all the shares in the folder and cache them. + // This is more performant than iterating each files afterwards. + $folderNode = $collection->getNode(); + $this->cachedFolders[$collection->getPath()] = true; + foreach ($this->getSharesFolder($folderNode) as $id => $shares) { + $this->cachedShares[$id] = $shares; + } + } + /** * Adds shares to propfind response * @@ -170,24 +200,6 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { return; } - // If the node is a directory and we are requesting share types or sharees - // then we get all the shares in the folder and cache them. - // This is more performant than iterating each files afterwards. - if ($sabreNode instanceof Directory - && $propFind->getDepth() !== 0 - && ( - !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) - || !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME)) - ) - ) { - $folderNode = $sabreNode->getNode(); - $this->cachedFolders[] = $sabreNode->getPath(); - $childShares = $this->getSharesFolder($folderNode); - foreach ($childShares as $id => $shares) { - $this->cachedShares[$id] = $shares; - } - } - $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode): ShareTypeList { $shares = $this->getShares($sabreNode); diff --git a/apps/dav/lib/Connector/Sabre/TagsPlugin.php b/apps/dav/lib/Connector/Sabre/TagsPlugin.php index 25c1633df36..ec3e6fc5320 100644 --- a/apps/dav/lib/Connector/Sabre/TagsPlugin.php +++ b/apps/dav/lib/Connector/Sabre/TagsPlugin.php @@ -31,6 +31,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\ITagManager; use OCP\ITags; use OCP\IUserSession; +use Sabre\DAV\ICollection; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; @@ -61,6 +62,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { * @var array */ private $cachedTags; + private array $cachedDirectories; /** * @param \Sabre\DAV\Tree $tree tree @@ -92,6 +94,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { $server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class; $this->server = $server; + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'handleGetProperties']); $this->server->on('propPatch', [$this, 'handleUpdateProperties']); $this->server->on('preloadProperties', [$this, 'handlePreloadProperties']); @@ -194,6 +197,29 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { } } + private function preloadCollection(PropFind $propFind, ICollection $collection): + void { + if (!($collection instanceof Node)) { + return; + } + + // need prefetch ? + if ($collection instanceof Directory + && !isset($this->cachedDirectories[$collection->getPath()]) + && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) + || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) + )) { + // note: pre-fetching only supported for depth <= 1 + $folderContent = $collection->getChildren(); + $fileIds = [(int)$collection->getId()]; + foreach ($folderContent as $info) { + $fileIds[] = (int)$info->getId(); + } + $this->prefetchTagsForFileIds($fileIds); + $this->cachedDirectories[$collection->getPath()] = true; + } + } + /** * Adds tags and favorites properties to the response, * if requested. @@ -210,21 +236,6 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { return; } - // need prefetch ? - if ($node instanceof Directory - && $propFind->getDepth() !== 0 - && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) - || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) - )) { - // note: pre-fetching only supported for depth <= 1 - $folderContent = $node->getChildren(); - $fileIds = [(int)$node->getId()]; - foreach ($folderContent as $info) { - $fileIds[] = (int)$info->getId(); - } - $this->prefetchTagsForFileIds($fileIds); - } - $isFav = null; $propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) { diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index f3fff11b3da..be7345f25df 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -9,13 +9,20 @@ namespace OCA\DAV\DAV; use Exception; +use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarHome; +use OCA\DAV\CalDAV\CalendarObject; use OCA\DAV\CalDAV\DefaultCalendarValidator; +use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use OCA\DAV\CalDAV\Outbox; +use OCA\DAV\CalDAV\Trashbin\TrashbinHome; use OCA\DAV\Connector\Sabre\Directory; -use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCA\DAV\Db\PropertyMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; +use Sabre\CalDAV\Schedule\Inbox; use Sabre\DAV\Exception as DavException; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; @@ -66,38 +73,16 @@ class CustomPropertiesBackend implements BackendInterface { '{DAV:}getetag', '{DAV:}quota-used-bytes', '{DAV:}quota-available-bytes', - '{http://owncloud.org/ns}permissions', - '{http://owncloud.org/ns}downloadURL', - '{http://owncloud.org/ns}dDC', - '{http://owncloud.org/ns}size', - '{http://nextcloud.org/ns}is-encrypted', - - // Currently, returning null from any propfind handler would still trigger the backend, - // so we add all known Nextcloud custom properties in here to avoid that - - // text app - '{http://nextcloud.org/ns}rich-workspace', - '{http://nextcloud.org/ns}rich-workspace-file', - // groupfolders - '{http://nextcloud.org/ns}acl-enabled', - '{http://nextcloud.org/ns}acl-can-manage', - '{http://nextcloud.org/ns}acl-list', - '{http://nextcloud.org/ns}inherited-acl-list', - '{http://nextcloud.org/ns}group-folder-id', - // files_lock - '{http://nextcloud.org/ns}lock', - '{http://nextcloud.org/ns}lock-owner-type', - '{http://nextcloud.org/ns}lock-owner', - '{http://nextcloud.org/ns}lock-owner-displayname', - '{http://nextcloud.org/ns}lock-owner-editor', - '{http://nextcloud.org/ns}lock-time', - '{http://nextcloud.org/ns}lock-timeout', - '{http://nextcloud.org/ns}lock-token', - // photos - '{http://nextcloud.org/ns}realpath', - '{http://nextcloud.org/ns}nbItems', - '{http://nextcloud.org/ns}face-detections', - '{http://nextcloud.org/ns}face-preview-image', + ]; + + /** + * Allowed properties for the oc/nc namespace, all other properties in the namespace are ignored + * + * @var string[] + */ + private const ALLOWED_NC_PROPERTIES = [ + '{http://owncloud.org/ns}calendar-enabled', + '{http://owncloud.org/ns}enabled', ]; /** @@ -119,11 +104,17 @@ class CustomPropertiesBackend implements BackendInterface { ]; /** + * Map of well-known property names to default values + */ + private const PROPERTY_DEFAULT_VALUES = [ + '{http://owncloud.org/ns}calendar-enabled' => '1', + ]; + + /** * Properties cache - * - * @var array */ - private $userCache = []; + private array $userCache = []; + private array $publishedCache = []; private XmlService $xmlService; /** @@ -136,6 +127,7 @@ class CustomPropertiesBackend implements BackendInterface { private Tree $tree, private IDBConnection $connection, private IUser $user, + private PropertyMapper $propertyMapper, private DefaultCalendarValidator $defaultCalendarValidator, ) { $this->xmlService = new XmlService(); @@ -155,14 +147,9 @@ class CustomPropertiesBackend implements BackendInterface { public function propFind($path, PropFind $propFind) { $requestedProps = $propFind->get404Properties(); - // these might appear - $requestedProps = array_diff( - $requestedProps, - self::IGNORED_PROPERTIES, - ); $requestedProps = array_filter( $requestedProps, - fn ($prop) => !str_starts_with($prop, FilesPlugin::FILE_METADATA_PREFIX), + $this->isPropertyAllowed(...), ); // substr of calendars/ => path is inside the CalDAV component @@ -224,6 +211,18 @@ class CustomPropertiesBackend implements BackendInterface { $this->cacheDirectory($path, $node); } + if ($node instanceof CalendarHome && $propFind->getDepth() !== 0) { + $backend = $node->getCalDAVBackend(); + if ($backend instanceof CalDavBackend) { + $this->cacheCalendars($node, $requestedProps); + } + } + + if ($node instanceof CalendarObject) { + // No custom properties supported on individual events + return; + } + // First fetch the published properties (set by another user), then get the ones set by // the current user. If both are set then the latter as priority. foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) { @@ -244,6 +243,16 @@ class CustomPropertiesBackend implements BackendInterface { } } + private function isPropertyAllowed(string $property): bool { + if (in_array($property, self::IGNORED_PROPERTIES)) { + return false; + } + if (str_starts_with($property, '{http://owncloud.org/ns}') || str_starts_with($property, '{http://nextcloud.org/ns}')) { + return in_array($property, self::ALLOWED_NC_PROPERTIES); + } + return true; + } + /** * Updates properties for a path * @@ -328,6 +337,10 @@ class CustomPropertiesBackend implements BackendInterface { return []; } + if (isset($this->publishedCache[$path])) { + return $this->publishedCache[$path]; + } + $qb = $this->connection->getQueryBuilder(); $qb->select('*') ->from(self::TABLE_NAME) @@ -338,6 +351,7 @@ class CustomPropertiesBackend implements BackendInterface { $props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']); } $result->closeCursor(); + $this->publishedCache[$path] = $props; return $props; } @@ -376,6 +390,62 @@ class CustomPropertiesBackend implements BackendInterface { $this->userCache = array_merge($this->userCache, $propsByPath); } + private function cacheCalendars(CalendarHome $node, array $requestedProperties): void { + $calendars = $node->getChildren(); + + $users = []; + foreach ($calendars as $calendar) { + if ($calendar instanceof Calendar) { + $user = str_replace('principals/users/', '', $calendar->getPrincipalURI()); + if (!isset($users[$user])) { + $users[$user] = ['calendars/' . $user]; + } + $users[$user][] = 'calendars/' . $user . '/' . $calendar->getUri(); + } elseif ($calendar instanceof Inbox || $calendar instanceof Outbox || $calendar instanceof TrashbinHome || $calendar instanceof ExternalCalendar) { + if ($calendar->getOwner()) { + $user = str_replace('principals/users/', '', $calendar->getOwner()); + if (!isset($users[$user])) { + $users[$user] = ['calendars/' . $user]; + } + $users[$user][] = 'calendars/' . $user . '/' . $calendar->getName(); + } + } + } + + // user properties + $properties = $this->propertyMapper->findPropertiesByPathsAndUsers($users); + + $propsByPath = []; + foreach ($users as $paths) { + foreach ($paths as $path) { + $propsByPath[$path] = []; + } + } + + foreach ($properties as $property) { + $propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype()); + } + $this->userCache = array_merge($this->userCache, $propsByPath); + + // published properties + $allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties); + if (empty($allowedProps)) { + return; + } + $paths = []; + foreach ($users as $nestedPaths) { + $paths = array_merge($paths, $nestedPaths); + } + $paths = array_unique($paths); + + $propsByPath = array_fill_keys(array_values($paths), []); + $properties = $this->propertyMapper->findPropertiesByPaths($paths, $allowedProps); + foreach ($properties as $property) { + $propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype()); + } + $this->publishedCache = array_merge($this->publishedCache, $propsByPath); + } + /** * Returns a list of properties for the given path and current user * @@ -422,6 +492,14 @@ class CustomPropertiesBackend implements BackendInterface { return $props; } + private function isPropertyDefaultValue(string $name, mixed $value): bool { + if (!isset(self::PROPERTY_DEFAULT_VALUES[$name])) { + return false; + } + + return self::PROPERTY_DEFAULT_VALUES[$name] === $value; + } + /** * @throws Exception */ @@ -438,8 +516,8 @@ class CustomPropertiesBackend implements BackendInterface { 'propertyName' => $propertyName, ]; - // If it was null, we need to delete the property - if (is_null($propertyValue)) { + // If it was null or set to the default value, we need to delete the property + if (is_null($propertyValue) || $this->isPropertyDefaultValue($propertyName, $propertyValue)) { if (array_key_exists($propertyName, $existing)) { $deleteQuery = $deleteQuery ?? $this->createDeleteQuery(); $deleteQuery diff --git a/apps/dav/lib/DAV/Sharing/Plugin.php b/apps/dav/lib/DAV/Sharing/Plugin.php index 03e63813bab..82b000bc8ce 100644 --- a/apps/dav/lib/DAV/Sharing/Plugin.php +++ b/apps/dav/lib/DAV/Sharing/Plugin.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Http; use OCP\IConfig; use OCP\IRequest; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -89,6 +90,7 @@ class Plugin extends ServerPlugin { $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = Invite::class; $this->server->on('method:POST', [$this, 'httpPost']); + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'propFind']); } @@ -168,6 +170,24 @@ class Plugin extends ServerPlugin { } } + private function preloadCollection(PropFind $propFind, ICollection $collection): void { + if (!$collection instanceof CalendarHome || $propFind->getDepth() !== 1) { + return; + } + + $backend = $collection->getCalDAVBackend(); + if (!$backend instanceof CalDavBackend) { + return; + } + + $calendars = $collection->getChildren(); + $calendars = array_filter($calendars, static fn (INode $node) => $node instanceof IShareable); + /** @var int[] $resourceIds */ + $resourceIds = array_map( + static fn (IShareable $node) => $node->getResourceId(), $calendars); + $backend->preloadShares($resourceIds); + } + /** * This event is triggered when properties are requested for a certain * node. @@ -179,20 +199,6 @@ class Plugin extends ServerPlugin { * @return void */ public function propFind(PropFind $propFind, INode $node) { - if ($node instanceof CalendarHome && $propFind->getDepth() === 1) { - $backend = $node->getCalDAVBackend(); - if ($backend instanceof CalDavBackend) { - $calendars = $node->getChildren(); - $calendars = array_filter($calendars, function (INode $node) { - return $node instanceof IShareable; - }); - /** @var int[] $resourceIds */ - $resourceIds = array_map(function (IShareable $node) { - return $node->getResourceId(); - }, $calendars); - $backend->preloadShares($resourceIds); - } - } if ($node instanceof IShareable) { $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function () use ($node) { return new Invite( diff --git a/apps/dav/lib/Db/Property.php b/apps/dav/lib/Db/Property.php index 96c5f75ef4f..6c1e249ac47 100644 --- a/apps/dav/lib/Db/Property.php +++ b/apps/dav/lib/Db/Property.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Db\Entity; * @method string getPropertypath() * @method string getPropertyname() * @method string getPropertyvalue() + * @method int getValuetype() */ class Property extends Entity { diff --git a/apps/dav/lib/Db/PropertyMapper.php b/apps/dav/lib/Db/PropertyMapper.php index 1789194ee7a..a3dbdaa7d98 100644 --- a/apps/dav/lib/Db/PropertyMapper.php +++ b/apps/dav/lib/Db/PropertyMapper.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace OCA\DAV\Db; use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; /** @@ -39,17 +40,43 @@ class PropertyMapper extends QBMapper { } /** + * @param array<string, string[]> $calendars * @return Property[] + * @throws \OCP\DB\Exception */ - public function findPropertiesByPath(string $userId, string $path): array { + public function findPropertiesByPathsAndUsers(array $calendars): array { $selectQb = $this->db->getQueryBuilder(); $selectQb->select('*') - ->from(self::TABLE_NAME) - ->where( - $selectQb->expr()->eq('userid', $selectQb->createNamedParameter($userId)), - $selectQb->expr()->eq('propertypath', $selectQb->createNamedParameter($path)), + ->from(self::TABLE_NAME); + + foreach ($calendars as $user => $paths) { + $selectQb->orWhere( + $selectQb->expr()->andX( + $selectQb->expr()->eq('userid', $selectQb->createNamedParameter($user)), + $selectQb->expr()->in('propertypath', $selectQb->createNamedParameter($paths, IQueryBuilder::PARAM_STR_ARRAY)), + ) ); + } + return $this->findEntities($selectQb); } + /** + * @param string[] $calendars + * @param string[] $allowedProperties + * @return Property[] + * @throws \OCP\DB\Exception + */ + public function findPropertiesByPaths(array $calendars, array $allowedProperties = []): array { + $selectQb = $this->db->getQueryBuilder(); + $selectQb->select('*') + ->from(self::TABLE_NAME) + ->where($selectQb->expr()->in('propertypath', $selectQb->createNamedParameter($calendars, IQueryBuilder::PARAM_STR_ARRAY))); + + if ($allowedProperties) { + $selectQb->andWhere($selectQb->expr()->in('propertyname', $selectQb->createNamedParameter($allowedProperties, IQueryBuilder::PARAM_STR_ARRAY))); + } + + return $this->findEntities($selectQb); + } } diff --git a/apps/dav/lib/Migration/Version1034Date20250813093701.php b/apps/dav/lib/Migration/Version1034Date20250813093701.php new file mode 100644 index 00000000000..10be71f067b --- /dev/null +++ b/apps/dav/lib/Migration/Version1034Date20250813093701.php @@ -0,0 +1,53 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; +use Override; + +class Version1034Date20250813093701 extends SimpleMigrationStep { + public function __construct( + private IDBConnection $db, + ) { + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + #[Override] + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + $qb = $this->db->getQueryBuilder(); + $qb->delete('properties') + ->where($qb->expr()->eq( + 'propertyname', + $qb->createNamedParameter( + '{http://owncloud.org/ns}calendar-enabled', + IQueryBuilder::PARAM_STR, + ), + IQueryBuilder::PARAM_STR, + )) + ->andWhere($qb->expr()->eq( + 'propertyvalue', + $qb->createNamedParameter( + '1', + IQueryBuilder::PARAM_STR, + ), + IQueryBuilder::PARAM_STR, + )) + ->executeStatement(); + } +} diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index f81c7fa6f29..9b4a1b3d33c 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -45,6 +45,8 @@ use OCA\DAV\Connector\Sabre\FilesReportPlugin; use OCA\DAV\Connector\Sabre\LockPlugin; use OCA\DAV\Connector\Sabre\MaintenancePlugin; use OCA\DAV\Connector\Sabre\PropfindCompressionPlugin; +use OCA\DAV\Connector\Sabre\PropFindMonitorPlugin; +use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin; use OCA\DAV\Connector\Sabre\QuotaPlugin; use OCA\DAV\Connector\Sabre\RequestIdHeaderPlugin; use OCA\DAV\Connector\Sabre\SharesPlugin; @@ -53,6 +55,7 @@ use OCA\DAV\Connector\Sabre\ZipFolderPlugin; use OCA\DAV\DAV\CustomPropertiesBackend; use OCA\DAV\DAV\PublicAuth; use OCA\DAV\DAV\ViewOnlyPlugin; +use OCA\DAV\Db\PropertyMapper; use OCA\DAV\Events\SabrePluginAddEvent; use OCA\DAV\Events\SabrePluginAuthInitEvent; use OCA\DAV\Files\BrowserErrorPagePlugin; @@ -108,6 +111,7 @@ class Server { private IRequest $request, private string $baseUri, ) { + $debugEnabled = \OCP\Server::get(IConfig::class)->getSystemValue('debug', false); $this->profiler = \OCP\Server::get(IProfiler::class); if ($this->profiler->isEnabled()) { /** @var IEventLogger $eventLogger */ @@ -120,6 +124,7 @@ class Server { $root = new RootCollection(); $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root)); + $this->server->setLogger($logger); // Add maintenance plugin $this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav'))); @@ -167,7 +172,9 @@ class Server { $authPlugin->addBackend($authBackend); // debugging - if (\OCP\Server::get(IConfig::class)->getSystemValue('debug', false)) { + if ($debugEnabled) { + $this->server->debugEnabled = true; + $this->server->addPlugin(new PropFindMonitorPlugin()); $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin()); } else { $this->server->addPlugin(new DummyGetResponsePlugin()); @@ -232,6 +239,7 @@ class Server { \OCP\Server::get(IUserSession::class) )); + // performance improvement plugins $this->server->addPlugin(new CopyEtagHeaderPlugin()); $this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class))); $this->server->addPlugin(new UploadAutoMkcolPlugin()); @@ -243,6 +251,7 @@ class Server { $eventDispatcher, )); $this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class)); + $this->server->addPlugin(new PropFindPreloadNotifyPlugin()); // allow setup of additional plugins $eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event); @@ -301,6 +310,7 @@ class Server { $this->server->tree, \OCP\Server::get(IDBConnection::class), \OCP\Server::get(IUserSession::class)->getUser(), + \OCP\Server::get(PropertyMapper::class), \OCP\Server::get(DefaultCalendarValidator::class), ) ) diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 4d4499c7559..6be3e8bd1a2 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -27,6 +27,7 @@ use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Conflict; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\UnsupportedMediaType; +use Sabre\DAV\ICollection; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\HTTP\RequestInterface; @@ -94,6 +95,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { $server->protectedProperties[] = self::ID_PROPERTYNAME; + $server->on('preloadCollection', $this->preloadCollection(...)); $server->on('propFind', [$this, 'handleGetProperties']); $server->on('propPatch', [$this, 'handleUpdateProperties']); $server->on('method:POST', [$this, 'httpPost']); @@ -199,6 +201,40 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { } } + private function preloadCollection( + PropFind $propFind, + ICollection $collection, + ): void { + if (!$collection instanceof Node) { + return; + } + + if ($collection instanceof Directory + && !isset($this->cachedTagMappings[$collection->getId()]) + && $propFind->getStatus( + self::SYSTEM_TAGS_PROPERTYNAME + ) !== null) { + $fileIds = [$collection->getId()]; + + // note: pre-fetching only supported for depth <= 1 + $folderContent = $collection->getChildren(); + foreach ($folderContent as $info) { + if ($info instanceof Node) { + $fileIds[] = $info->getId(); + } + } + + $tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files'); + + $this->cachedTagMappings += $tags; + $emptyFileIds = array_diff($fileIds, array_keys($tags)); + + // also cache the ones that were not found + foreach ($emptyFileIds as $fileId) { + $this->cachedTagMappings[$fileId] = []; + } + } + } /** * Retrieves system tag properties @@ -297,29 +333,6 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { } private function propfindForFile(PropFind $propFind, Node $node): void { - if ($node instanceof Directory - && $propFind->getDepth() !== 0 - && !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) { - $fileIds = [$node->getId()]; - - // note: pre-fetching only supported for depth <= 1 - $folderContent = $node->getChildren(); - foreach ($folderContent as $info) { - if ($info instanceof Node) { - $fileIds[] = $info->getId(); - } - } - - $tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files'); - - $this->cachedTagMappings = $this->cachedTagMappings + $tags; - $emptyFileIds = array_diff($fileIds, array_keys($tags)); - - // also cache the ones that were not found - foreach ($emptyFileIds as $fileId) { - $this->cachedTagMappings[$fileId] = []; - } - } $propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) { $user = $this->userSession->getUser(); diff --git a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php index d4021a66299..cafbdd3ca40 100644 --- a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php @@ -12,6 +12,7 @@ use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\DAV\CustomPropertiesBackend; +use OCA\DAV\Db\PropertyMapper; use OCP\IDBConnection; use OCP\IUser; use OCP\Server; @@ -52,6 +53,7 @@ class CustomPropertiesBackendTest extends \Test\TestCase { $this->tree, Server::get(IDBConnection::class), $this->user, + Server::get(PropertyMapper::class), $this->defaultCalendarValidator, ); } diff --git a/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php new file mode 100644 index 00000000000..9d22befa201 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php @@ -0,0 +1,133 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\PropFindMonitorPlugin; +use OCA\DAV\Connector\Sabre\Server; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; +use Test\TestCase; + +class PropFindMonitorPluginTest extends TestCase { + + private PropFindMonitorPlugin $plugin; + private Server&MockObject $server; + private LoggerInterface&MockObject $logger; + private Request&MockObject $request; + private Response&MockObject $response; + + public static function dataTest(): array { + $minQueriesTrigger = PropFindMonitorPlugin::THRESHOLD_QUERY_FACTOR + * PropFindMonitorPlugin::THRESHOLD_NODES; + return [ + 'No queries logged' => [[], 0], + 'Plugins with queries in less than threshold nodes should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => 100, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1] + ], + [], + ] + ], + 0 + ], + 'Plugins with query-to-node ratio less than threshold should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger - 1, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES ], + ], + [], + ] + ], + 0 + ], + 'Plugins with more nodes scanned than queries executed should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES * 2], + ], + [],] + ], + 0 + ], + 'Plugins with queries only in highest depth level should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1 + ] + ], + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger * 2, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES + ] + ], + ] + ], + 0 + ], + 'Plugins with too many queries should be logged' => [ + [ + 'propFind' => [ + [ + 'FirstPlugin' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES, + ], + 'SecondPlugin' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES, + ] + ], + [], + ] + ], + 2 + ] + ]; + } + + /** + * @dataProvider dataTest + */ + public function test(array $queries, $expectedLogCalls): void { + $this->plugin->initialize($this->server); + $this->server->expects($this->once())->method('getPluginQueries') + ->willReturn($queries); + + $this->server->expects(empty($queries) ? $this->never() : $this->once()) + ->method('getLogger') + ->willReturn($this->logger); + + $this->logger->expects($this->exactly($expectedLogCalls))->method('error'); + $this->plugin->afterResponse($this->request, $this->response); + } + + protected function setUp(): void { + parent::setUp(); + + $this->plugin = new PropFindMonitorPlugin(); + $this->server = $this->createMock(Server::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->request = $this->createMock(Request::class); + $this->response = $this->createMock(Response::class); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php new file mode 100644 index 00000000000..52fe3eba5bf --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\ICollection; +use Sabre\DAV\IFile; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Test\TestCase; + +class PropFindPreloadNotifyPluginTest extends TestCase { + + private Server&MockObject $server; + private PropFindPreloadNotifyPlugin $plugin; + + protected function setUp(): void { + parent::setUp(); + + $this->server = $this->createMock(Server::class); + $this->plugin = new PropFindPreloadNotifyPlugin(); + } + + public function testInitialize(): void { + $this->server + ->expects(self::once()) + ->method('on') + ->with('propFind', + $this->anything(), 1); + $this->plugin->initialize($this->server); + } + + public static function dataTestCollectionPreloadNotifier(): array { + return [ + 'When node is not a collection, should not emit' => [ + IFile::class, + 1, + false, + true + ], + 'When node is a collection but depth is zero, should not emit' => [ + ICollection::class, + 0, + false, + true + ], + 'When node is a collection, and depth > 0, should emit' => [ + ICollection::class, + 1, + true, + true + ], + 'When node is a collection, and depth is infinite, should emit' + => [ + ICollection::class, + Server::DEPTH_INFINITY, + true, + true + ], + 'When called called handler returns false, it should be returned' + => [ + ICollection::class, + 1, + true, + false + ] + ]; + } + + #[DataProvider(methodName: 'dataTestCollectionPreloadNotifier')] + public function testCollectionPreloadNotifier(string $nodeType, int $depth, bool $shouldEmit, bool $emitReturns): + void { + $this->plugin->initialize($this->server); + $propFind = $this->createMock(PropFind::class); + $propFind->expects(self::any())->method('getDepth')->willReturn($depth); + $node = $this->createMock($nodeType); + + $expectation = $shouldEmit ? self::once() : self::never(); + $this->server->expects($expectation)->method('emit')->with('preloadCollection', + [$propFind, $node])->willReturn($emitReturns); + $return = $this->plugin->collectionPreloadNotifier($propFind, $node); + $this->assertEquals($emitReturns, $return); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php index 1c8e29dab38..33f579eb913 100644 --- a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php @@ -223,6 +223,7 @@ class SharesPluginTest extends \Test\TestCase { 0 ); + $this->server->emit('preloadCollection', [$propFindRoot, $sabreNode]); $this->plugin->handleGetProperties( $propFindRoot, $sabreNode diff --git a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php index 5003280bfdc..554a4a1424e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php @@ -147,6 +147,8 @@ class TagsPluginTest extends \Test\TestCase { 0 ); + $this->server->emit('preloadCollection', [$propFindRoot, $node]); + $this->plugin->handleGetProperties( $propFindRoot, $node diff --git a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php index 2a85c0cbecd..517969fc9a3 100644 --- a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php @@ -10,6 +10,7 @@ namespace OCA\DAV\Tests\unit\DAV; use OCA\DAV\CalDAV\Calendar; use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\DAV\CustomPropertiesBackend; +use OCA\DAV\Db\PropertyMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; @@ -36,6 +37,7 @@ class CustomPropertiesBackendTest extends TestCase { private IUser&MockObject $user; private DefaultCalendarValidator&MockObject $defaultCalendarValidator; private CustomPropertiesBackend $backend; + private PropertyMapper $propertyMapper; protected function setUp(): void { parent::setUp(); @@ -49,6 +51,7 @@ class CustomPropertiesBackendTest extends TestCase { ->with() ->willReturn('dummy_user_42'); $this->dbConnection = \OCP\Server::get(IDBConnection::class); + $this->propertyMapper = \OCP\Server::get(PropertyMapper::class); $this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class); $this->backend = new CustomPropertiesBackend( @@ -56,6 +59,7 @@ class CustomPropertiesBackendTest extends TestCase { $this->tree, $this->dbConnection, $this->user, + $this->propertyMapper, $this->defaultCalendarValidator, ); } @@ -129,6 +133,7 @@ class CustomPropertiesBackendTest extends TestCase { $this->tree, $db, $this->user, + $this->propertyMapper, $this->defaultCalendarValidator, ); diff --git a/apps/federatedfilesharing/l10n/pt_BR.js b/apps/federatedfilesharing/l10n/pt_BR.js index b6af476dffe..4d09230d39e 100644 --- a/apps/federatedfilesharing/l10n/pt_BR.js +++ b/apps/federatedfilesharing/l10n/pt_BR.js @@ -47,6 +47,7 @@ OC.L10N.register( "Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Compartilhe comigo por meio do meu ID de Nuvem Federada #Nextcloud, consulte {url}", "Share with me through my #Nextcloud Federated Cloud ID" : "Compartilhe comigo através do meu ID de Nuvem Federada #Nextcloud", "Share with me via Nextcloud" : "Compartilhe comigo via Nextcloud", + "Cloud ID copied" : "ID da nuvem copiada", "Copy" : "Copiar", "Clipboard not available. Please copy the cloud ID manually." : "Área de transferência indisponível. Copie o ID de Nuvem manualmente.", "Copied!" : "Copiado!", diff --git a/apps/federatedfilesharing/l10n/pt_BR.json b/apps/federatedfilesharing/l10n/pt_BR.json index b668cdc5513..7d7c89d5329 100644 --- a/apps/federatedfilesharing/l10n/pt_BR.json +++ b/apps/federatedfilesharing/l10n/pt_BR.json @@ -45,6 +45,7 @@ "Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Compartilhe comigo por meio do meu ID de Nuvem Federada #Nextcloud, consulte {url}", "Share with me through my #Nextcloud Federated Cloud ID" : "Compartilhe comigo através do meu ID de Nuvem Federada #Nextcloud", "Share with me via Nextcloud" : "Compartilhe comigo via Nextcloud", + "Cloud ID copied" : "ID da nuvem copiada", "Copy" : "Copiar", "Clipboard not available. Please copy the cloud ID manually." : "Área de transferência indisponível. Copie o ID de Nuvem manualmente.", "Copied!" : "Copiado!", diff --git a/apps/federatedfilesharing/l10n/sr.js b/apps/federatedfilesharing/l10n/sr.js index d64d4e59d19..2200c42b9ee 100644 --- a/apps/federatedfilesharing/l10n/sr.js +++ b/apps/federatedfilesharing/l10n/sr.js @@ -58,6 +58,7 @@ OC.L10N.register( "X (formerly Twitter)" : "X (бивши Twitter)", "formerly Twitter" : "бивши Twitter", "Mastodon" : "Mastodon", + "Bluesky" : "Bluesky", "Add to your website" : "Додај на свој веб сајт", "HTML Code:" : "ХТМЛ кôд:", "Cancel" : "Одустани", diff --git a/apps/federatedfilesharing/l10n/sr.json b/apps/federatedfilesharing/l10n/sr.json index c8816b63785..85e3a9ca73f 100644 --- a/apps/federatedfilesharing/l10n/sr.json +++ b/apps/federatedfilesharing/l10n/sr.json @@ -56,6 +56,7 @@ "X (formerly Twitter)" : "X (бивши Twitter)", "formerly Twitter" : "бивши Twitter", "Mastodon" : "Mastodon", + "Bluesky" : "Bluesky", "Add to your website" : "Додај на свој веб сајт", "HTML Code:" : "ХТМЛ кôд:", "Cancel" : "Одустани", diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index aedcd5b7ed5..fb53cef79b8 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -53,6 +53,8 @@ <command>OCA\Files\Command\Object\Info</command> <command>OCA\Files\Command\Object\ListObject</command> <command>OCA\Files\Command\Object\Orphans</command> + <command>OCA\Files\Command\Object\Multi\Users</command> + <command>OCA\Files\Command\Object\Multi\Rename</command> <command>OCA\Files\Command\WindowsCompatibleFilenames</command> </commands> diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 070cb46de38..0c0f734251f 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -37,6 +37,8 @@ return array( 'OCA\\Files\\Command\\Object\\Get' => $baseDir . '/../lib/Command/Object/Get.php', 'OCA\\Files\\Command\\Object\\Info' => $baseDir . '/../lib/Command/Object/Info.php', 'OCA\\Files\\Command\\Object\\ListObject' => $baseDir . '/../lib/Command/Object/ListObject.php', + 'OCA\\Files\\Command\\Object\\Multi\\Rename' => $baseDir . '/../lib/Command/Object/Multi/Rename.php', + 'OCA\\Files\\Command\\Object\\Multi\\Users' => $baseDir . '/../lib/Command/Object/Multi/Users.php', 'OCA\\Files\\Command\\Object\\ObjectUtil' => $baseDir . '/../lib/Command/Object/ObjectUtil.php', 'OCA\\Files\\Command\\Object\\Orphans' => $baseDir . '/../lib/Command/Object/Orphans.php', 'OCA\\Files\\Command\\Object\\Put' => $baseDir . '/../lib/Command/Object/Put.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index ce79d370e7c..19310ed4e92 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -52,6 +52,8 @@ class ComposerStaticInitFiles 'OCA\\Files\\Command\\Object\\Get' => __DIR__ . '/..' . '/../lib/Command/Object/Get.php', 'OCA\\Files\\Command\\Object\\Info' => __DIR__ . '/..' . '/../lib/Command/Object/Info.php', 'OCA\\Files\\Command\\Object\\ListObject' => __DIR__ . '/..' . '/../lib/Command/Object/ListObject.php', + 'OCA\\Files\\Command\\Object\\Multi\\Rename' => __DIR__ . '/..' . '/../lib/Command/Object/Multi/Rename.php', + 'OCA\\Files\\Command\\Object\\Multi\\Users' => __DIR__ . '/..' . '/../lib/Command/Object/Multi/Users.php', 'OCA\\Files\\Command\\Object\\ObjectUtil' => __DIR__ . '/..' . '/../lib/Command/Object/ObjectUtil.php', 'OCA\\Files\\Command\\Object\\Orphans' => __DIR__ . '/..' . '/../lib/Command/Object/Orphans.php', 'OCA\\Files\\Command\\Object\\Put' => __DIR__ . '/..' . '/../lib/Command/Object/Put.php', diff --git a/apps/files/l10n/be.js b/apps/files/l10n/be.js new file mode 100644 index 00000000000..1c72fa29839 --- /dev/null +++ b/apps/files/l10n/be.js @@ -0,0 +1,258 @@ +OC.L10N.register( + "files", + { + "Added to favorites" : "Дададзены ў абранае", + "Removed from favorites" : "Выдалены з абранага", + "You added {file} to your favorites" : "Вы дадалі {file} у абранае", + "You removed {file} from your favorites" : "Вы выдалілі {file} з абранага", + "Favorites" : "Абранае", + "File changes" : "Змены ў файлах", + "Created by {user}" : "Створаны карыстальнікам {user}", + "Changed by {user}" : "Зменены карыстальнікам {user}", + "Deleted by {user}" : "Выдалены карыстальнікам {user}", + "Restored by {user}" : "Адноўлены карыстальнікам {user}", + "Renamed by {user}" : "Перайменаваны карыстальнікам {user}", + "Moved by {user}" : "Перамешчаны карыстальнікам {user}", + "\"remote account\"" : "\"аддалены ўліковы запіс\"", + "You created {file}" : "Вы стварылі {file}", + "You created an encrypted file in {file}" : "Вы стварылі зашыфраваны файл у {file}", + "{user} created {file}" : "{user} стварыў(-ла) {file}", + "{user} created an encrypted file in {file}" : "{user} стварыў(-ла) зашыфраваны файл у {file}", + "You changed {file}" : "Вы змянілі {file}", + "You changed an encrypted file in {file}" : "Вы змянілі зашыфраваны файл у {file}", + "{user} changed {file}" : "{user} змяніў(-ла) {file}", + "{user} changed an encrypted file in {file}" : "{user} змяніў(-ла) зашыфраваны файл у {file}", + "You deleted {file}" : "Вы выдалілі {file}", + "You deleted an encrypted file in {file}" : "Вы выдалілі зашыфраваны файл у {file}", + "{user} deleted {file}" : "{user} выдаліў(-ла) {file}", + "{user} deleted an encrypted file in {file}" : "{user} выдаліў(-ла) зашыфраваны файл у {file}", + "You restored {file}" : "Вы аднавілі {file}", + "{user} restored {file}" : "{user} аднавіў(-ла) {file}", + "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Вы перайменавалі {oldfile} (схаваны) у {newfile} (схаваны)", + "You renamed {oldfile} (hidden) to {newfile}" : "Вы перайменавалі {oldfile} (схаваны) у {newfile}", + "You renamed {oldfile} to {newfile} (hidden)" : "Вы перайменавалі {oldfile} у {newfile} (схаваны)", + "You renamed {oldfile} to {newfile}" : "Вы перайменавалі {oldfile} у {newfile}", + "{user} renamed {oldfile} (hidden) to {newfile} (hidden)" : "{user} перайменаваў(-ла) {oldfile} (схаваны) у {newfile} (схаваны)", + "{user} renamed {oldfile} (hidden) to {newfile}" : "{user} перайменаваў(-ла) {oldfile} (схаваны) ў {newfile}", + "{user} renamed {oldfile} to {newfile} (hidden)" : "{user} перайменаваў(-ла) {oldfile} у {newfile} (схаваны)", + "{user} renamed {oldfile} to {newfile}" : "{user} перайменаваў(-ла) {oldfile} у {newfile}", + "You moved {oldfile} to {newfile}" : "Вы перамясцілі {oldfile} у {newfile}", + "{user} moved {oldfile} to {newfile}" : "{user} перамясціў(-ла) {oldfile} у {newfile}", + "A file has been added to or removed from your <strong>favorites</strong>" : "Файл быў дададзены або выдалены з <strong>абраных</strong>", + "Files" : "Файлы", + "Folder not found" : "Папка не знойдзена", + "The file cannot be found" : "Файл не знойдзены", + "The destination path does not exist: %1$s" : "Шлях прызначэння не існуе: %1$s", + "Favorite files" : "Абраныя файлы", + "No favorites" : "Няма абраных", + "More favorites" : "Больш абраных", + "Accept" : "Прыняць", + "Reject" : "Адхіліць", + "in %s" : "у %s", + "Files compatibility" : "Сумяшчальнасць файлаў", + "File Management" : "Кіраванне файламі", + "Home" : "Дадому", + "Target folder does not exist any more" : "Мэтавая папка больш не існуе", + "Reload current directory" : "Перазагрузіць бягучы каталог", + "Go to the \"{dir}\" directory" : "Перайсці да каталога \"{dir}\"", + "Drag and drop files here to upload" : "Перацягніце файлы сюды, каб запампаваць іх", + "Favorite" : "Абранае", + "Back" : "Назад", + "File is loading" : "Файл загружаецца", + "Folder is loading" : "Папка загружаецца", + "Filename" : "Назва файла", + "Folder name" : "Назва папкі", + "Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" перайменаваны ў \"{newName}\"", + "Rename file" : "Перайменаваць файл", + "Folder" : "Папка", + "Unknown file type" : "Невядомы тып файла", + "{ext} image" : "Відарыс {ext}", + "{ext} video" : "Відэа {ext}", + "{ext} audio" : "Аўдыя {ext} ", + "{ext} text" : "Тэкст {ext}", + "Pending" : "У чаканні", + "Unknown date" : "Невядомая дата", + "Clear filter" : "Ачысціць фільтр", + "Modified" : "Зменены", + "Type" : "Тып", + "Active filters" : "Актыўныя фільтры", + "Remove filter" : "Выдаліць фільтр", + "Name" : "Назва", + "File type" : "Тып файла", + "Size" : "Памер", + "Actions" : "Дзеянні", + "List of files and folders." : "Спіс файлаў і папак.", + "File not found" : "Файл не знойдзены", + "{usedQuotaByte} used" : "Выкарыстана {usedQuotaByte}", + "{used} of {quota} used" : "Выкарыстана {used} з {quota}", + "{relative}% used" : "Выкарыстана {relative}%", + "New folder" : "Новая папка", + "Create new folder" : "Стварыць новую папку", + "This name is already in use." : "Гэта назва ўжо выкарыстоўваецца.", + "Create" : "Стварыць", + "Fill template fields" : "Запоўніце палі шаблону", + "Unknown error" : "Невядомая памылка", + "Change" : "Змяніць", + "New owner" : "Новы ўладальнік", + "Share" : "Абагуліць", + "Upload was cancelled by user" : "Карыстальнік скасаваў запампоўванне", + "Go back" : "Назад", + "Your files" : "Вашы файлы", + "File cannot be accessed" : "Немагчыма атрымаць доступ да файла", + "The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Файл не знойдзены, або ў вас няма правоў на яго прагляд. Папрасіце адпраўніка абагуліць яго.", + "No search results for “{query}”" : "Няма вынікаў пошуку па запыце \"{query}\"", + "Search for files" : "Пошук файлаў", + "Clipboard is not available" : "Буфер абмену недаступны", + "WebDAV URL copied" : "URL-адрас WebDAV скапіяваны", + "General" : "Агульныя", + "All files" : "Усе файлы", + "Personal files" : "Асабістыя файлы", + "Appearance" : "Знешні выгляд", + "Show file extensions" : "Паказваць пашырэнні файлаў", + "WebDAV" : "WebDAV", + "WebDAV URL" : "URL-адрас WebDAV", + "Copy" : "Капіяваць", + "Warnings" : "Папярэджанні", + "Keyboard shortcuts" : "Спалучэнні клавіш", + "File actions" : "Дзеянні з файламі", + "Rename" : "Перайменаваць", + "Delete" : "Выдаліць", + "Manage tags" : "Кіраванне тэгамі", + "Select all files" : "Выбраць усе файлы", + "Navigation" : "Навігацыя", + "You" : "Вы", + "Error while loading the file data" : "Памылка пры загрузцы даных файла", + "Owner" : "Уладальнік", + "Remove from favorites" : "Выдаліць з абранага", + "Add to favorites" : "У абранае", + "Tags" : "Тэгі", + "Unable to create new file from template" : "Немагчыма стварыць новы файл з шаблона", + "Pick a template for {name}" : "Выберыце шаблон для {name}", + "Create a new file with the selected template" : "Стварыць файл на падставе выбранага шаблона", + "Creating file" : "Стварэнне файла", + "Save as {displayName}" : "Захаваць як {displayName}", + "Save as …" : "Захаваць як …", + "Converting files …" : "Канвертацыя файлаў …", + "Converting file …" : "Канвертацыя файла …", + "File successfully converted" : "Файл паспяхова сканвертаваны", + "Delete permanently" : "Выдаліць назаўжды", + "Delete file" : "Выдаліць файл", + "Delete files" : "Выдаліць файлы", + "Delete folder" : "Выдаліць папку", + "Delete folders" : "Выдаліць папкі", + "_You are about to permanently delete {count} item_::_You are about to permanently delete {count} items_" : ["Вы збіраецеся назаўжды выдаліць {count} элемент","Вы збіраецеся назаўжды выдаліць {count} элементы","Вы збіраецеся назаўжды выдаліць {count} элементаў","Вы збіраецеся назаўжды выдаліць {count} элементаў"], + "_You are about to delete {count} item_::_You are about to delete {count} items_" : ["Вы збіраецеся выдаліць {count} элемент","Вы збіраецеся выдаліць {count} элементы","Вы збіраецеся выдаліць {count} элементаў","Вы збіраецеся выдаліць {count} элементаў"], + "Confirm deletion" : "Пацвердзіць выдаленне", + "Cancel" : "Скасаваць", + "Download" : "Спампаваць", + "Moving \"{source}\" to \"{destination}\" …" : "Перамяшчэнне \"{source}\" у \"{destination}\" …", + "Copying \"{source}\" to \"{destination}\" …" : "Капіяванне \"{source}\" у \"{destination}\" …", + "This file/folder is already in that directory" : "Гэты файл/папка ўжо знаходзіцца ў гэтым каталогу", + "(copy)" : "(копія)", + "(copy %n)" : "(копія %n)", + "A file or folder with that name already exists in this folder" : "Файл або папка з такой назвай ужо існуе ў гэтай папцы", + "The files are locked" : "Файлы заблакіраваны", + "The file does not exist anymore" : "Файл больш не існуе", + "Choose destination" : "Выберыце прызначэнне", + "Copy to {target}" : "Капіяваць у {target}", + "Move to {target}" : "Перамясціць у {target}", + "Move" : "Перамясціць", + "Move or copy" : "Перамясціць або капіяваць", + "Open folder {displayName}" : "Адкрыць папку {displayName}", + "Open locally" : "Адкрыць лакальна", + "Open file locally" : "Адкрыць файл лакальна", + "Today" : "Сёння", + "Last 7 days" : "Апошнія 7 дзён", + "Last 30 days" : "Апошнія 30 дзён", + "This year ({year})" : "Гэты год ({year})", + "Last year ({year})" : "Мінулы год ({year})", + "Documents" : "Дакументы", + "Spreadsheets" : "Табліцы", + "Presentations" : "Прэзентацыі", + "PDFs" : "PDF-файлы", + "Folders" : "Папкі", + "Audio" : "Аўдыя", + "Images" : "Відарысы", + "Videos" : "Відэа", + "Created new folder \"{name}\"" : "Створана новая папка \"{name}\"", + "Unable to initialize the templates directory" : "Немагчыма ініцыялізаваць каталог шаблонаў", + "Create templates folder" : "Стварыць папку шаблонаў", + "Templates" : "Шаблоны", + "New template folder" : "Новая папка шаблонаў", + "In folder" : "У папцы", + "One of the dropped files could not be processed" : "Адзін з перацягнутых файлаў не ўдалося апрацаваць", + "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Ваш браўзер не падтрымлівае Filesystem API. Каталогі не будуць запампаваныя", + "No files to upload" : "Няма файлаў для запампоўвання", + "Unable to create the directory {directory}" : "Немагчыма стварыць папку {directory}", + "Some files could not be uploaded" : "Некаторыя файлы не ўдалося запампаваць", + "Files uploaded successfully" : "Файлы паспяхова запампаваныя", + "No files to process" : "Няма файлаў для апрацоўкі", + "Some files could not be copied" : "Некаторыя файлы не ўдалося скапіяваць", + "Some files could not be moved" : "Некаторыя файлы не ўдалося перамясціць", + "Files copied successfully" : "Файлы паспяхова скапіяваны", + "Files moved successfully" : "Файлы паспяхова перамешчаны", + "Conflicts resolution skipped" : "Рашэнне канфліктаў прапушчана", + "Upload cancelled" : "Запампоўванне скасавана", + "Could not rename \"{oldName}\"" : "Не атрымалася перайменаваць \"{oldName}\"", + "Unexpected error: {error}" : "Нечаканая памылка: {error}", + "_%n file_::_%n files_" : ["%n файл","%n файлы","%n файлаў","%n файлаў"], + "_%n folder_::_%n folders_" : ["%n папка","%n папкі","%n папак","%n папак"], + "_%n hidden_::_%n hidden_" : ["%n схаваны","%n схаваныя","%n схаваных","%n схаваных"], + "Filename must not be empty." : "Назва файла не можа быць пустой.", + "No favorites yet" : "Пакуль няма абраных", + "List of your files and folders." : "Спіс вашых файлаў і папак.", + "List of your files and folders that are not shared." : "Спіс вашых неабагуленых файлаў і папак.", + "No personal files found" : "Асабістых файлаў не знойдзена", + "Files that are not shared will show up here." : "Тут будуць адлюстроўвацца неабагуленыя файлы.", + "Recent" : "Нядаўнія", + "List of recently modified files and folders." : "Спіс нядаўна змененых файлаў і папак.", + "No recently modified files" : "Няма нядаўна змененых файлаў", + "Files and folders you recently modified will show up here." : "Тут будуць адлюстроўвацца нядаўна змененыя вамі файлы і папкі.", + "Search" : "Пошук", + "File could not be found" : "Файл не знойдзены", + "Close" : "Закрыць", + "Could not create folder \"{dir}\"" : "Не ўдалося стварыць папку \"{dir}\"", + "This will stop your current uploads." : "Гэта спыніць вашы бягучыя запампоўкі.", + "Upload cancelled." : "Запампоўванне скасавана.", + "Processing files …" : "Апрацоўка файлаў …", + "…" : "…", + "Unable to upload {filename} as it is a directory or has 0 bytes" : "Немагчыма запампаваць {filename}, бо гэта каталог або ён мае памер 0 байтаў", + "An unknown error has occurred" : "Узнікла невядомая памылка", + "File could not be uploaded" : "Не ўдалося запампаваць файл", + "Uploading …" : "Запампоўванне …", + "{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})", + "Uploading … ({currentNumber}/{total})" : "Запампоўванне … ({currentNumber}/{total})", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} з {totalSize} ({bitrate})", + "Set reminder" : "Задаць напамін", + "Edit locally" : "Рэдагаваць лакальна", + "Open" : "Адкрыць", + "Unable to determine date" : "Немагчыма вызначыць дату", + "copy" : "копія", + "{newName} already exists" : "{newName} ужо існуе", + "{dirs} and {files}" : "{dirs} і {files}", + "_Uploading %n file_::_Uploading %n files_" : ["Запампоўванне %n файла","Запампоўванне %n файлаў","Запампоўванне %n файлаў","Запампоўванне %n файлаў"], + "New" : "Новы", + "{used}%" : "{used}%", + "{used} used" : "Выкарыстана {used}", + "Path" : "Шлях", + "Upload file" : "Запампаваць файл", + "WebDAV URL copied to clipboard" : "URL-адрас WebDAV скапіяваны ў буфер абмену", + "Copy to clipboard" : "Капіяваць у буфер абмену", + "Deletion cancelled" : "Выдаленне скасавана", + "Move cancelled" : "Перамяшчэнне скасавана", + "Photos and images" : "Фота і відарысы", + "New folder creation cancelled" : "Стварэнне новай папкі скасавана", + "_{folderCount} folder_::_{folderCount} folders_" : ["{folderCount} папка","{folderCount} папкі","{folderCount} папак","{folderCount} папак"], + "_{fileCount} file_::_{fileCount} files_" : ["{fileCount} файл","{fileCount} файлы","{fileCount} файлаў","{fileCount} файлаў"], + "_1 file and {folderCount} folder_::_1 file and {folderCount} folders_" : ["1 файл і {folderCount} папка","1 файл і {folderCount} папкі","1 файл і {folderCount} папак","1 файл і {folderCount} папак"], + "_{fileCount} file and 1 folder_::_{fileCount} files and 1 folder_" : ["{fileCount} файл і 1 папка","{fileCount} файлы і 1 папка","{fileCount} файлаў і 1 папка","{fileCount} файлаў і 1 папка"], + "All folders" : "Усе папкі", + "Personal Files" : "Асабістыя файлы", + "Text file" : "Тэкставы файл", + "%1$s (renamed)" : "%1$s (перайменаваны)", + "Rename a file" : "Перайменаваць файл", + "Delete a file" : "Выдаліць файл", + "Deselect all files" : "Скасаваць выбар усіх файлаў", + "Select a range of files" : "Выберыце дыяпазон файлаў" +}, +"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/apps/files/l10n/be.json b/apps/files/l10n/be.json new file mode 100644 index 00000000000..7cc4d30cdac --- /dev/null +++ b/apps/files/l10n/be.json @@ -0,0 +1,256 @@ +{ "translations": { + "Added to favorites" : "Дададзены ў абранае", + "Removed from favorites" : "Выдалены з абранага", + "You added {file} to your favorites" : "Вы дадалі {file} у абранае", + "You removed {file} from your favorites" : "Вы выдалілі {file} з абранага", + "Favorites" : "Абранае", + "File changes" : "Змены ў файлах", + "Created by {user}" : "Створаны карыстальнікам {user}", + "Changed by {user}" : "Зменены карыстальнікам {user}", + "Deleted by {user}" : "Выдалены карыстальнікам {user}", + "Restored by {user}" : "Адноўлены карыстальнікам {user}", + "Renamed by {user}" : "Перайменаваны карыстальнікам {user}", + "Moved by {user}" : "Перамешчаны карыстальнікам {user}", + "\"remote account\"" : "\"аддалены ўліковы запіс\"", + "You created {file}" : "Вы стварылі {file}", + "You created an encrypted file in {file}" : "Вы стварылі зашыфраваны файл у {file}", + "{user} created {file}" : "{user} стварыў(-ла) {file}", + "{user} created an encrypted file in {file}" : "{user} стварыў(-ла) зашыфраваны файл у {file}", + "You changed {file}" : "Вы змянілі {file}", + "You changed an encrypted file in {file}" : "Вы змянілі зашыфраваны файл у {file}", + "{user} changed {file}" : "{user} змяніў(-ла) {file}", + "{user} changed an encrypted file in {file}" : "{user} змяніў(-ла) зашыфраваны файл у {file}", + "You deleted {file}" : "Вы выдалілі {file}", + "You deleted an encrypted file in {file}" : "Вы выдалілі зашыфраваны файл у {file}", + "{user} deleted {file}" : "{user} выдаліў(-ла) {file}", + "{user} deleted an encrypted file in {file}" : "{user} выдаліў(-ла) зашыфраваны файл у {file}", + "You restored {file}" : "Вы аднавілі {file}", + "{user} restored {file}" : "{user} аднавіў(-ла) {file}", + "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Вы перайменавалі {oldfile} (схаваны) у {newfile} (схаваны)", + "You renamed {oldfile} (hidden) to {newfile}" : "Вы перайменавалі {oldfile} (схаваны) у {newfile}", + "You renamed {oldfile} to {newfile} (hidden)" : "Вы перайменавалі {oldfile} у {newfile} (схаваны)", + "You renamed {oldfile} to {newfile}" : "Вы перайменавалі {oldfile} у {newfile}", + "{user} renamed {oldfile} (hidden) to {newfile} (hidden)" : "{user} перайменаваў(-ла) {oldfile} (схаваны) у {newfile} (схаваны)", + "{user} renamed {oldfile} (hidden) to {newfile}" : "{user} перайменаваў(-ла) {oldfile} (схаваны) ў {newfile}", + "{user} renamed {oldfile} to {newfile} (hidden)" : "{user} перайменаваў(-ла) {oldfile} у {newfile} (схаваны)", + "{user} renamed {oldfile} to {newfile}" : "{user} перайменаваў(-ла) {oldfile} у {newfile}", + "You moved {oldfile} to {newfile}" : "Вы перамясцілі {oldfile} у {newfile}", + "{user} moved {oldfile} to {newfile}" : "{user} перамясціў(-ла) {oldfile} у {newfile}", + "A file has been added to or removed from your <strong>favorites</strong>" : "Файл быў дададзены або выдалены з <strong>абраных</strong>", + "Files" : "Файлы", + "Folder not found" : "Папка не знойдзена", + "The file cannot be found" : "Файл не знойдзены", + "The destination path does not exist: %1$s" : "Шлях прызначэння не існуе: %1$s", + "Favorite files" : "Абраныя файлы", + "No favorites" : "Няма абраных", + "More favorites" : "Больш абраных", + "Accept" : "Прыняць", + "Reject" : "Адхіліць", + "in %s" : "у %s", + "Files compatibility" : "Сумяшчальнасць файлаў", + "File Management" : "Кіраванне файламі", + "Home" : "Дадому", + "Target folder does not exist any more" : "Мэтавая папка больш не існуе", + "Reload current directory" : "Перазагрузіць бягучы каталог", + "Go to the \"{dir}\" directory" : "Перайсці да каталога \"{dir}\"", + "Drag and drop files here to upload" : "Перацягніце файлы сюды, каб запампаваць іх", + "Favorite" : "Абранае", + "Back" : "Назад", + "File is loading" : "Файл загружаецца", + "Folder is loading" : "Папка загружаецца", + "Filename" : "Назва файла", + "Folder name" : "Назва папкі", + "Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" перайменаваны ў \"{newName}\"", + "Rename file" : "Перайменаваць файл", + "Folder" : "Папка", + "Unknown file type" : "Невядомы тып файла", + "{ext} image" : "Відарыс {ext}", + "{ext} video" : "Відэа {ext}", + "{ext} audio" : "Аўдыя {ext} ", + "{ext} text" : "Тэкст {ext}", + "Pending" : "У чаканні", + "Unknown date" : "Невядомая дата", + "Clear filter" : "Ачысціць фільтр", + "Modified" : "Зменены", + "Type" : "Тып", + "Active filters" : "Актыўныя фільтры", + "Remove filter" : "Выдаліць фільтр", + "Name" : "Назва", + "File type" : "Тып файла", + "Size" : "Памер", + "Actions" : "Дзеянні", + "List of files and folders." : "Спіс файлаў і папак.", + "File not found" : "Файл не знойдзены", + "{usedQuotaByte} used" : "Выкарыстана {usedQuotaByte}", + "{used} of {quota} used" : "Выкарыстана {used} з {quota}", + "{relative}% used" : "Выкарыстана {relative}%", + "New folder" : "Новая папка", + "Create new folder" : "Стварыць новую папку", + "This name is already in use." : "Гэта назва ўжо выкарыстоўваецца.", + "Create" : "Стварыць", + "Fill template fields" : "Запоўніце палі шаблону", + "Unknown error" : "Невядомая памылка", + "Change" : "Змяніць", + "New owner" : "Новы ўладальнік", + "Share" : "Абагуліць", + "Upload was cancelled by user" : "Карыстальнік скасаваў запампоўванне", + "Go back" : "Назад", + "Your files" : "Вашы файлы", + "File cannot be accessed" : "Немагчыма атрымаць доступ да файла", + "The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Файл не знойдзены, або ў вас няма правоў на яго прагляд. Папрасіце адпраўніка абагуліць яго.", + "No search results for “{query}”" : "Няма вынікаў пошуку па запыце \"{query}\"", + "Search for files" : "Пошук файлаў", + "Clipboard is not available" : "Буфер абмену недаступны", + "WebDAV URL copied" : "URL-адрас WebDAV скапіяваны", + "General" : "Агульныя", + "All files" : "Усе файлы", + "Personal files" : "Асабістыя файлы", + "Appearance" : "Знешні выгляд", + "Show file extensions" : "Паказваць пашырэнні файлаў", + "WebDAV" : "WebDAV", + "WebDAV URL" : "URL-адрас WebDAV", + "Copy" : "Капіяваць", + "Warnings" : "Папярэджанні", + "Keyboard shortcuts" : "Спалучэнні клавіш", + "File actions" : "Дзеянні з файламі", + "Rename" : "Перайменаваць", + "Delete" : "Выдаліць", + "Manage tags" : "Кіраванне тэгамі", + "Select all files" : "Выбраць усе файлы", + "Navigation" : "Навігацыя", + "You" : "Вы", + "Error while loading the file data" : "Памылка пры загрузцы даных файла", + "Owner" : "Уладальнік", + "Remove from favorites" : "Выдаліць з абранага", + "Add to favorites" : "У абранае", + "Tags" : "Тэгі", + "Unable to create new file from template" : "Немагчыма стварыць новы файл з шаблона", + "Pick a template for {name}" : "Выберыце шаблон для {name}", + "Create a new file with the selected template" : "Стварыць файл на падставе выбранага шаблона", + "Creating file" : "Стварэнне файла", + "Save as {displayName}" : "Захаваць як {displayName}", + "Save as …" : "Захаваць як …", + "Converting files …" : "Канвертацыя файлаў …", + "Converting file …" : "Канвертацыя файла …", + "File successfully converted" : "Файл паспяхова сканвертаваны", + "Delete permanently" : "Выдаліць назаўжды", + "Delete file" : "Выдаліць файл", + "Delete files" : "Выдаліць файлы", + "Delete folder" : "Выдаліць папку", + "Delete folders" : "Выдаліць папкі", + "_You are about to permanently delete {count} item_::_You are about to permanently delete {count} items_" : ["Вы збіраецеся назаўжды выдаліць {count} элемент","Вы збіраецеся назаўжды выдаліць {count} элементы","Вы збіраецеся назаўжды выдаліць {count} элементаў","Вы збіраецеся назаўжды выдаліць {count} элементаў"], + "_You are about to delete {count} item_::_You are about to delete {count} items_" : ["Вы збіраецеся выдаліць {count} элемент","Вы збіраецеся выдаліць {count} элементы","Вы збіраецеся выдаліць {count} элементаў","Вы збіраецеся выдаліць {count} элементаў"], + "Confirm deletion" : "Пацвердзіць выдаленне", + "Cancel" : "Скасаваць", + "Download" : "Спампаваць", + "Moving \"{source}\" to \"{destination}\" …" : "Перамяшчэнне \"{source}\" у \"{destination}\" …", + "Copying \"{source}\" to \"{destination}\" …" : "Капіяванне \"{source}\" у \"{destination}\" …", + "This file/folder is already in that directory" : "Гэты файл/папка ўжо знаходзіцца ў гэтым каталогу", + "(copy)" : "(копія)", + "(copy %n)" : "(копія %n)", + "A file or folder with that name already exists in this folder" : "Файл або папка з такой назвай ужо існуе ў гэтай папцы", + "The files are locked" : "Файлы заблакіраваны", + "The file does not exist anymore" : "Файл больш не існуе", + "Choose destination" : "Выберыце прызначэнне", + "Copy to {target}" : "Капіяваць у {target}", + "Move to {target}" : "Перамясціць у {target}", + "Move" : "Перамясціць", + "Move or copy" : "Перамясціць або капіяваць", + "Open folder {displayName}" : "Адкрыць папку {displayName}", + "Open locally" : "Адкрыць лакальна", + "Open file locally" : "Адкрыць файл лакальна", + "Today" : "Сёння", + "Last 7 days" : "Апошнія 7 дзён", + "Last 30 days" : "Апошнія 30 дзён", + "This year ({year})" : "Гэты год ({year})", + "Last year ({year})" : "Мінулы год ({year})", + "Documents" : "Дакументы", + "Spreadsheets" : "Табліцы", + "Presentations" : "Прэзентацыі", + "PDFs" : "PDF-файлы", + "Folders" : "Папкі", + "Audio" : "Аўдыя", + "Images" : "Відарысы", + "Videos" : "Відэа", + "Created new folder \"{name}\"" : "Створана новая папка \"{name}\"", + "Unable to initialize the templates directory" : "Немагчыма ініцыялізаваць каталог шаблонаў", + "Create templates folder" : "Стварыць папку шаблонаў", + "Templates" : "Шаблоны", + "New template folder" : "Новая папка шаблонаў", + "In folder" : "У папцы", + "One of the dropped files could not be processed" : "Адзін з перацягнутых файлаў не ўдалося апрацаваць", + "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Ваш браўзер не падтрымлівае Filesystem API. Каталогі не будуць запампаваныя", + "No files to upload" : "Няма файлаў для запампоўвання", + "Unable to create the directory {directory}" : "Немагчыма стварыць папку {directory}", + "Some files could not be uploaded" : "Некаторыя файлы не ўдалося запампаваць", + "Files uploaded successfully" : "Файлы паспяхова запампаваныя", + "No files to process" : "Няма файлаў для апрацоўкі", + "Some files could not be copied" : "Некаторыя файлы не ўдалося скапіяваць", + "Some files could not be moved" : "Некаторыя файлы не ўдалося перамясціць", + "Files copied successfully" : "Файлы паспяхова скапіяваны", + "Files moved successfully" : "Файлы паспяхова перамешчаны", + "Conflicts resolution skipped" : "Рашэнне канфліктаў прапушчана", + "Upload cancelled" : "Запампоўванне скасавана", + "Could not rename \"{oldName}\"" : "Не атрымалася перайменаваць \"{oldName}\"", + "Unexpected error: {error}" : "Нечаканая памылка: {error}", + "_%n file_::_%n files_" : ["%n файл","%n файлы","%n файлаў","%n файлаў"], + "_%n folder_::_%n folders_" : ["%n папка","%n папкі","%n папак","%n папак"], + "_%n hidden_::_%n hidden_" : ["%n схаваны","%n схаваныя","%n схаваных","%n схаваных"], + "Filename must not be empty." : "Назва файла не можа быць пустой.", + "No favorites yet" : "Пакуль няма абраных", + "List of your files and folders." : "Спіс вашых файлаў і папак.", + "List of your files and folders that are not shared." : "Спіс вашых неабагуленых файлаў і папак.", + "No personal files found" : "Асабістых файлаў не знойдзена", + "Files that are not shared will show up here." : "Тут будуць адлюстроўвацца неабагуленыя файлы.", + "Recent" : "Нядаўнія", + "List of recently modified files and folders." : "Спіс нядаўна змененых файлаў і папак.", + "No recently modified files" : "Няма нядаўна змененых файлаў", + "Files and folders you recently modified will show up here." : "Тут будуць адлюстроўвацца нядаўна змененыя вамі файлы і папкі.", + "Search" : "Пошук", + "File could not be found" : "Файл не знойдзены", + "Close" : "Закрыць", + "Could not create folder \"{dir}\"" : "Не ўдалося стварыць папку \"{dir}\"", + "This will stop your current uploads." : "Гэта спыніць вашы бягучыя запампоўкі.", + "Upload cancelled." : "Запампоўванне скасавана.", + "Processing files …" : "Апрацоўка файлаў …", + "…" : "…", + "Unable to upload {filename} as it is a directory or has 0 bytes" : "Немагчыма запампаваць {filename}, бо гэта каталог або ён мае памер 0 байтаў", + "An unknown error has occurred" : "Узнікла невядомая памылка", + "File could not be uploaded" : "Не ўдалося запампаваць файл", + "Uploading …" : "Запампоўванне …", + "{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})", + "Uploading … ({currentNumber}/{total})" : "Запампоўванне … ({currentNumber}/{total})", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} з {totalSize} ({bitrate})", + "Set reminder" : "Задаць напамін", + "Edit locally" : "Рэдагаваць лакальна", + "Open" : "Адкрыць", + "Unable to determine date" : "Немагчыма вызначыць дату", + "copy" : "копія", + "{newName} already exists" : "{newName} ужо існуе", + "{dirs} and {files}" : "{dirs} і {files}", + "_Uploading %n file_::_Uploading %n files_" : ["Запампоўванне %n файла","Запампоўванне %n файлаў","Запампоўванне %n файлаў","Запампоўванне %n файлаў"], + "New" : "Новы", + "{used}%" : "{used}%", + "{used} used" : "Выкарыстана {used}", + "Path" : "Шлях", + "Upload file" : "Запампаваць файл", + "WebDAV URL copied to clipboard" : "URL-адрас WebDAV скапіяваны ў буфер абмену", + "Copy to clipboard" : "Капіяваць у буфер абмену", + "Deletion cancelled" : "Выдаленне скасавана", + "Move cancelled" : "Перамяшчэнне скасавана", + "Photos and images" : "Фота і відарысы", + "New folder creation cancelled" : "Стварэнне новай папкі скасавана", + "_{folderCount} folder_::_{folderCount} folders_" : ["{folderCount} папка","{folderCount} папкі","{folderCount} папак","{folderCount} папак"], + "_{fileCount} file_::_{fileCount} files_" : ["{fileCount} файл","{fileCount} файлы","{fileCount} файлаў","{fileCount} файлаў"], + "_1 file and {folderCount} folder_::_1 file and {folderCount} folders_" : ["1 файл і {folderCount} папка","1 файл і {folderCount} папкі","1 файл і {folderCount} папак","1 файл і {folderCount} папак"], + "_{fileCount} file and 1 folder_::_{fileCount} files and 1 folder_" : ["{fileCount} файл і 1 папка","{fileCount} файлы і 1 папка","{fileCount} файлаў і 1 папка","{fileCount} файлаў і 1 папка"], + "All folders" : "Усе папкі", + "Personal Files" : "Асабістыя файлы", + "Text file" : "Тэкставы файл", + "%1$s (renamed)" : "%1$s (перайменаваны)", + "Rename a file" : "Перайменаваць файл", + "Delete a file" : "Выдаліць файл", + "Deselect all files" : "Скасаваць выбар усіх файлаў", + "Select a range of files" : "Выберыце дыяпазон файлаў" +},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" +}
\ No newline at end of file diff --git a/apps/files/l10n/de_DE.js b/apps/files/l10n/de_DE.js index 4b897553f31..f5b58fcde18 100644 --- a/apps/files/l10n/de_DE.js +++ b/apps/files/l10n/de_DE.js @@ -115,7 +115,7 @@ OC.L10N.register( "Name" : "Name", "File type" : "Dateityp", "Size" : "Größe", - "{displayName}: failed on some elements" : "{displayName}: Ist bei einigen Elementen fehlgeschlagen", + "{displayName}: failed on some elements" : "{displayName}: Ist bei einigen Elementen fehlgeschlagen", "{displayName}: done" : "{displayName}: Abgeschlossen", "{displayName}: failed" : "{displayName}: Fehlgeschlagen", "Actions" : "Aktionen", @@ -126,8 +126,8 @@ OC.L10N.register( "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Diese Liste ist aus Performance-Gründen nicht vollständig gerendert. Die Dateien werden gerendert, wenn Sie durch die Liste navigieren.", "File not found" : "Datei nicht gefunden", "_{count} selected_::_{count} selected_" : ["{count} ausgewählt","{count} ausgewählt"], - "Search everywhere …" : "Überall suchen …", - "Search here …" : "Hier suchen …", + "Search everywhere …" : "Überall suchen …", + "Search here …" : "Hier suchen …", "Search scope options" : "Suchbereichsoptionen", "Search here" : "Hier suchen", "{usedQuotaByte} used" : "{usedQuotaByte} verwendet", @@ -224,7 +224,7 @@ OC.L10N.register( "Selection" : "Auswahl", "Select all files" : "Alle Dateien auswählen", "Deselect all" : "Auswahl aufheben", - "Select or deselect" : "Aus- oder Abwählen", + "Select or deselect" : "Aus- oder abwählen", "Select a range" : "Einen Bereich auswählen", "Navigation" : "Navigation", "Go to parent folder" : "Zum übergeordneten Ordner wechseln", diff --git a/apps/files/l10n/de_DE.json b/apps/files/l10n/de_DE.json index 26b5abdbd75..403987f92f0 100644 --- a/apps/files/l10n/de_DE.json +++ b/apps/files/l10n/de_DE.json @@ -113,7 +113,7 @@ "Name" : "Name", "File type" : "Dateityp", "Size" : "Größe", - "{displayName}: failed on some elements" : "{displayName}: Ist bei einigen Elementen fehlgeschlagen", + "{displayName}: failed on some elements" : "{displayName}: Ist bei einigen Elementen fehlgeschlagen", "{displayName}: done" : "{displayName}: Abgeschlossen", "{displayName}: failed" : "{displayName}: Fehlgeschlagen", "Actions" : "Aktionen", @@ -124,8 +124,8 @@ "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Diese Liste ist aus Performance-Gründen nicht vollständig gerendert. Die Dateien werden gerendert, wenn Sie durch die Liste navigieren.", "File not found" : "Datei nicht gefunden", "_{count} selected_::_{count} selected_" : ["{count} ausgewählt","{count} ausgewählt"], - "Search everywhere …" : "Überall suchen …", - "Search here …" : "Hier suchen …", + "Search everywhere …" : "Überall suchen …", + "Search here …" : "Hier suchen …", "Search scope options" : "Suchbereichsoptionen", "Search here" : "Hier suchen", "{usedQuotaByte} used" : "{usedQuotaByte} verwendet", @@ -222,7 +222,7 @@ "Selection" : "Auswahl", "Select all files" : "Alle Dateien auswählen", "Deselect all" : "Auswahl aufheben", - "Select or deselect" : "Aus- oder Abwählen", + "Select or deselect" : "Aus- oder abwählen", "Select a range" : "Einen Bereich auswählen", "Navigation" : "Navigation", "Go to parent folder" : "Zum übergeordneten Ordner wechseln", diff --git a/apps/files/l10n/pt_BR.js b/apps/files/l10n/pt_BR.js index 6b64267ec74..2b19b2bf39c 100644 --- a/apps/files/l10n/pt_BR.js +++ b/apps/files/l10n/pt_BR.js @@ -115,6 +115,9 @@ OC.L10N.register( "Name" : "Nome", "File type" : "Tipo de arquivo", "Size" : "Tamanho", + "{displayName}: failed on some elements" : "{displayName}: falhou em alguns elementos", + "{displayName}: done" : "{displayName}: feito", + "{displayName}: failed" : "{displayName}: fracassado", "Actions" : "Ações", "(selected)" : "(selecionados)", "List of files and folders." : "Lista de arquivos e pastas.", @@ -123,7 +126,10 @@ OC.L10N.register( "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Esta lista não é totalmente renderizada por motivos de desempenho. Os arquivos serão renderizados à medida que você navegar pela lista.", "File not found" : "Arquivo não encontrado", "_{count} selected_::_{count} selected_" : ["{count} selecionado","{count} selecionados","{count} selecionados"], + "Search everywhere …" : "Pesquise em todos os lugares…", + "Search here …" : "Pesquise aqui…", "Search scope options" : "Opções de escopo da pesquisa", + "Search here" : "Pesquise aqui", "{usedQuotaByte} used" : "{usedQuotaByte} usado", "{used} of {quota} used" : "{used} de {quota} usados", "{relative}% used" : "{relative}% usado", @@ -187,6 +193,7 @@ OC.L10N.register( "No search results for “{query}”" : "Sem resultados de pesquisa para \"{query}\"", "Search for files" : "Pesquisar arquivos", "Clipboard is not available" : "A área de transferência não está disponível", + "WebDAV URL copied" : "URL do WebDAV copiada", "General" : "Geral", "Default view" : "Visualização padrão", "All files" : "Todos os arquivos", @@ -197,24 +204,37 @@ OC.L10N.register( "Appearance" : "Aparência", "Show hidden files" : "Mostrar arquivos ocultos", "Show file type column" : "Mostrar coluna de tipo de arquivo", + "Show file extensions" : "Mostrar extensões de arquivo", "Crop image previews" : "Cortar visualizações de imagem", "Additional settings" : "Configurações adicionais", "WebDAV" : "WebDAV", "WebDAV URL" : "URL WebDAV", "Copy" : "Copiar", + "How to access files using WebDAV" : "Como acessar arquivos usando WebDAV", "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "A Autenticação de Dois Fatores está ativada para sua conta e, portanto, você precisa usar uma senha de aplicativo para conectar um cliente WebDAV externo.", "Warnings" : "Avisos", + "Warn before changing a file extension" : "Avisar antes de alterar uma extensão de arquivo", + "Warn before deleting files" : "Avisar antes de excluir arquivos", "Keyboard shortcuts" : "Atalhos do teclado", "File actions" : "Ações de arquivos", "Rename" : "Renomear", "Delete" : "Excluir", + "Add or remove favorite" : "Adicionar ou remover favoritos", "Manage tags" : "Gerenciar etiquetas", "Selection" : "Seleção", "Select all files" : "Selecionar todos os arquivos", "Deselect all" : "Desselecionar todos", + "Select or deselect" : "Selecionar ou desmarcar", + "Select a range" : "Selecione um intervalo", "Navigation" : "Navegação", + "Go to parent folder" : "Ir para a pasta mãe", + "Go to file above" : "Vá para o arquivo acima", + "Go to file below" : "Vá para o arquivo abaixo", + "Go left in grid" : "Vá para a esquerda na grade", + "Go right in grid" : "Vá para a direita na grade", "View" : "Visualização", "Toggle grid view" : "Alternar a visão em grade", + "Open file sidebar" : "Abrir barra lateral de arquivo", "Show those shortcuts" : "Mostrar esses atalhos", "You" : "Você", "Shared multiple times with different people" : "Compartilhado várias vezes com pessoas diferentes", @@ -322,6 +342,7 @@ OC.L10N.register( "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "O nome \"{newName}\" já é utilizado na pasta \"{dir}\". Escolha um nome diferente.", "Could not rename \"{oldName}\"" : "Não foi possível renomear \"{oldName}\"", "This operation is forbidden" : "Esta operação é proibida", + "This folder is unavailable, please try again later or contact the administration" : "Esta pasta não está disponível, tente novamente mais tarde ou entre em contato com a administração", "Storage is temporarily not available" : "O armazenamento está temporariamente indisponível", "Unexpected error: {error}" : "Erro inesperado: {error}", "_%n file_::_%n files_" : ["%n arquivo","%n de arquivos","%n arquivos"], diff --git a/apps/files/l10n/pt_BR.json b/apps/files/l10n/pt_BR.json index dd50c32b179..55a603b6107 100644 --- a/apps/files/l10n/pt_BR.json +++ b/apps/files/l10n/pt_BR.json @@ -113,6 +113,9 @@ "Name" : "Nome", "File type" : "Tipo de arquivo", "Size" : "Tamanho", + "{displayName}: failed on some elements" : "{displayName}: falhou em alguns elementos", + "{displayName}: done" : "{displayName}: feito", + "{displayName}: failed" : "{displayName}: fracassado", "Actions" : "Ações", "(selected)" : "(selecionados)", "List of files and folders." : "Lista de arquivos e pastas.", @@ -121,7 +124,10 @@ "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Esta lista não é totalmente renderizada por motivos de desempenho. Os arquivos serão renderizados à medida que você navegar pela lista.", "File not found" : "Arquivo não encontrado", "_{count} selected_::_{count} selected_" : ["{count} selecionado","{count} selecionados","{count} selecionados"], + "Search everywhere …" : "Pesquise em todos os lugares…", + "Search here …" : "Pesquise aqui…", "Search scope options" : "Opções de escopo da pesquisa", + "Search here" : "Pesquise aqui", "{usedQuotaByte} used" : "{usedQuotaByte} usado", "{used} of {quota} used" : "{used} de {quota} usados", "{relative}% used" : "{relative}% usado", @@ -185,6 +191,7 @@ "No search results for “{query}”" : "Sem resultados de pesquisa para \"{query}\"", "Search for files" : "Pesquisar arquivos", "Clipboard is not available" : "A área de transferência não está disponível", + "WebDAV URL copied" : "URL do WebDAV copiada", "General" : "Geral", "Default view" : "Visualização padrão", "All files" : "Todos os arquivos", @@ -195,24 +202,37 @@ "Appearance" : "Aparência", "Show hidden files" : "Mostrar arquivos ocultos", "Show file type column" : "Mostrar coluna de tipo de arquivo", + "Show file extensions" : "Mostrar extensões de arquivo", "Crop image previews" : "Cortar visualizações de imagem", "Additional settings" : "Configurações adicionais", "WebDAV" : "WebDAV", "WebDAV URL" : "URL WebDAV", "Copy" : "Copiar", + "How to access files using WebDAV" : "Como acessar arquivos usando WebDAV", "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "A Autenticação de Dois Fatores está ativada para sua conta e, portanto, você precisa usar uma senha de aplicativo para conectar um cliente WebDAV externo.", "Warnings" : "Avisos", + "Warn before changing a file extension" : "Avisar antes de alterar uma extensão de arquivo", + "Warn before deleting files" : "Avisar antes de excluir arquivos", "Keyboard shortcuts" : "Atalhos do teclado", "File actions" : "Ações de arquivos", "Rename" : "Renomear", "Delete" : "Excluir", + "Add or remove favorite" : "Adicionar ou remover favoritos", "Manage tags" : "Gerenciar etiquetas", "Selection" : "Seleção", "Select all files" : "Selecionar todos os arquivos", "Deselect all" : "Desselecionar todos", + "Select or deselect" : "Selecionar ou desmarcar", + "Select a range" : "Selecione um intervalo", "Navigation" : "Navegação", + "Go to parent folder" : "Ir para a pasta mãe", + "Go to file above" : "Vá para o arquivo acima", + "Go to file below" : "Vá para o arquivo abaixo", + "Go left in grid" : "Vá para a esquerda na grade", + "Go right in grid" : "Vá para a direita na grade", "View" : "Visualização", "Toggle grid view" : "Alternar a visão em grade", + "Open file sidebar" : "Abrir barra lateral de arquivo", "Show those shortcuts" : "Mostrar esses atalhos", "You" : "Você", "Shared multiple times with different people" : "Compartilhado várias vezes com pessoas diferentes", @@ -320,6 +340,7 @@ "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "O nome \"{newName}\" já é utilizado na pasta \"{dir}\". Escolha um nome diferente.", "Could not rename \"{oldName}\"" : "Não foi possível renomear \"{oldName}\"", "This operation is forbidden" : "Esta operação é proibida", + "This folder is unavailable, please try again later or contact the administration" : "Esta pasta não está disponível, tente novamente mais tarde ou entre em contato com a administração", "Storage is temporarily not available" : "O armazenamento está temporariamente indisponível", "Unexpected error: {error}" : "Erro inesperado: {error}", "_%n file_::_%n files_" : ["%n arquivo","%n de arquivos","%n arquivos"], diff --git a/apps/files/l10n/sr.js b/apps/files/l10n/sr.js index da03fc7ccc4..50b07bbe6ca 100644 --- a/apps/files/l10n/sr.js +++ b/apps/files/l10n/sr.js @@ -115,6 +115,9 @@ OC.L10N.register( "Name" : "Назив", "File type" : "Тип фајла", "Size" : "Величина", + "{displayName}: failed on some elements" : "{displayName}: није успело на неким елементима", + "{displayName}: done" : "{displayName}: завршено", + "{displayName}: failed" : "{displayName}: није успело", "Actions" : "Радње", "(selected)" : "(изабрано)", "List of files and folders." : "Листа фајлова и фолдера.", @@ -123,7 +126,10 @@ OC.L10N.register( "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Ова листа није у потпуности приказана из разлога перформанси. Фајлови ће се приказивати како се крећете кроз листу.", "File not found" : "Фајл није нађен", "_{count} selected_::_{count} selected_" : ["изабран је {count}","изабрана су {count}","изабрано је {count}"], + "Search everywhere …" : "Претражи свуда", + "Search here …" : "Претражи овде…", "Search scope options" : "Опције опсега претраге", + "Search here" : "Претражи овде", "{usedQuotaByte} used" : "{usedQuotaByte} искоришћено", "{used} of {quota} used" : "{used} од {quota} искоришћено", "{relative}% used" : "{relative}% искоришћено", @@ -135,6 +141,7 @@ OC.L10N.register( "Create new folder" : "Направи нову фасциклу", "This name is already in use." : "Ово име се већ користи.", "Create" : "Направи", + "Files starting with a dot are hidden by default" : "Фајлови који почињу тачком су подразумевано скривени", "Fill template fields" : "Попуните поља шаблона", "Submitting fields …" : "Поља се подносе…", "Submit" : "Пошаљи", @@ -186,32 +193,48 @@ OC.L10N.register( "No search results for “{query}”" : "Није нађен ниједан резултат за „{query}”", "Search for files" : "Претражи фајлове", "Clipboard is not available" : "Клипборд није доступан", + "WebDAV URL copied" : "WebDAV URL је копиран", "General" : "Опште", "Default view" : "Подразумевани поглед", "All files" : "Сви фајлови", "Personal files" : "Лични фајлови", "Sort favorites first" : "Сортирај прво омиљене", "Sort folders before files" : "Поређај фолдере испред фајлова", + "Folder tree" : "Стабло фолдера", "Appearance" : "Изглед", "Show hidden files" : "Прикажи скривене фајлове", "Show file type column" : "Прикажи колону са типом фајла", + "Show file extensions" : "Прикажи екстензије фајла", "Crop image previews" : "Опсецање прегледа слика", "Additional settings" : "Додатне поставке", "WebDAV" : "ВебДАВ", "WebDAV URL" : "WebDAV URL", "Copy" : "Копирај", + "How to access files using WebDAV" : "Како да приступите фајловима преко WebDAV протокола", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "Двофакторска потврда идентитета је укључена за ваш налог, тако да морате користити лозинку апликације да повежете спољни WebDAV клијент.", "Warnings" : "Упозорења", + "Warn before changing a file extension" : "Упозори пре измене екстензије фајла", + "Warn before deleting files" : "Упозори пре брисања фајлова", "Keyboard shortcuts" : "Пречице на тастатури", "File actions" : "Фајл акције", "Rename" : "Преименуј", "Delete" : "Обриши", + "Add or remove favorite" : "Додај или уклони омиљене", "Manage tags" : "Управљање ознакама", "Selection" : "Избор", "Select all files" : "Изабери све фајлове", "Deselect all" : "Поништи цео избор", + "Select or deselect" : "Изабери или уклони избор", + "Select a range" : "Изабери опсег", "Navigation" : "Навигација", + "Go to parent folder" : "Иди на фолдер родитељ", + "Go to file above" : "Иди на фајл изнад", + "Go to file below" : "Иди на фајл испод", + "Go left in grid" : "Иди на лево у мрежи", + "Go right in grid" : "Иди на десно у мреж", "View" : "Погледај", "Toggle grid view" : "Укључи/искључи приказ мреже", + "Open file sidebar" : "Отвори бочну траку за фајл", "Show those shortcuts" : "Прикажи те пречице", "You" : "Ви", "Shared multiple times with different people" : "Дељено више пута са разним људима", @@ -300,6 +323,7 @@ OC.L10N.register( "Templates" : "Шаблони", "New template folder" : "Нови фолдер шаблона", "In folder" : "У фолдеру", + "Search in all files" : "Претражи у свим фајловима", "Search in folder: {folder}" : "Претрага у фолдеру: {folder}", "One of the dropped files could not be processed" : "Један од упуштених фајлова не може да се обради", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Ваш интернет прегледач не подржава Filesystem API. Нећете моћи да отпремате директоријуме", @@ -318,6 +342,7 @@ OC.L10N.register( "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Назив „{newName}” се већ користи у директоријуму „{dir}”. Молимо вас да изаберете неко друго име.", "Could not rename \"{oldName}\"" : "Не може да се промени име фајла „{oldName}”", "This operation is forbidden" : "Ова радња је забрањена", + "This folder is unavailable, please try again later or contact the administration" : "Овај фолдер није доступан, молимо вас да покушате касније или да контактирате администрацију", "Storage is temporarily not available" : "Складиште привремено није доступно", "Unexpected error: {error}" : "Неочекивана грешка: {error}", "_%n file_::_%n files_" : ["%n фајл","%n фајла","%n фајлова"], diff --git a/apps/files/l10n/sr.json b/apps/files/l10n/sr.json index b97d3ffc9b0..8746aa285a7 100644 --- a/apps/files/l10n/sr.json +++ b/apps/files/l10n/sr.json @@ -113,6 +113,9 @@ "Name" : "Назив", "File type" : "Тип фајла", "Size" : "Величина", + "{displayName}: failed on some elements" : "{displayName}: није успело на неким елементима", + "{displayName}: done" : "{displayName}: завршено", + "{displayName}: failed" : "{displayName}: није успело", "Actions" : "Радње", "(selected)" : "(изабрано)", "List of files and folders." : "Листа фајлова и фолдера.", @@ -121,7 +124,10 @@ "This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Ова листа није у потпуности приказана из разлога перформанси. Фајлови ће се приказивати како се крећете кроз листу.", "File not found" : "Фајл није нађен", "_{count} selected_::_{count} selected_" : ["изабран је {count}","изабрана су {count}","изабрано је {count}"], + "Search everywhere …" : "Претражи свуда", + "Search here …" : "Претражи овде…", "Search scope options" : "Опције опсега претраге", + "Search here" : "Претражи овде", "{usedQuotaByte} used" : "{usedQuotaByte} искоришћено", "{used} of {quota} used" : "{used} од {quota} искоришћено", "{relative}% used" : "{relative}% искоришћено", @@ -133,6 +139,7 @@ "Create new folder" : "Направи нову фасциклу", "This name is already in use." : "Ово име се већ користи.", "Create" : "Направи", + "Files starting with a dot are hidden by default" : "Фајлови који почињу тачком су подразумевано скривени", "Fill template fields" : "Попуните поља шаблона", "Submitting fields …" : "Поља се подносе…", "Submit" : "Пошаљи", @@ -184,32 +191,48 @@ "No search results for “{query}”" : "Није нађен ниједан резултат за „{query}”", "Search for files" : "Претражи фајлове", "Clipboard is not available" : "Клипборд није доступан", + "WebDAV URL copied" : "WebDAV URL је копиран", "General" : "Опште", "Default view" : "Подразумевани поглед", "All files" : "Сви фајлови", "Personal files" : "Лични фајлови", "Sort favorites first" : "Сортирај прво омиљене", "Sort folders before files" : "Поређај фолдере испред фајлова", + "Folder tree" : "Стабло фолдера", "Appearance" : "Изглед", "Show hidden files" : "Прикажи скривене фајлове", "Show file type column" : "Прикажи колону са типом фајла", + "Show file extensions" : "Прикажи екстензије фајла", "Crop image previews" : "Опсецање прегледа слика", "Additional settings" : "Додатне поставке", "WebDAV" : "ВебДАВ", "WebDAV URL" : "WebDAV URL", "Copy" : "Копирај", + "How to access files using WebDAV" : "Како да приступите фајловима преко WebDAV протокола", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "Двофакторска потврда идентитета је укључена за ваш налог, тако да морате користити лозинку апликације да повежете спољни WebDAV клијент.", "Warnings" : "Упозорења", + "Warn before changing a file extension" : "Упозори пре измене екстензије фајла", + "Warn before deleting files" : "Упозори пре брисања фајлова", "Keyboard shortcuts" : "Пречице на тастатури", "File actions" : "Фајл акције", "Rename" : "Преименуј", "Delete" : "Обриши", + "Add or remove favorite" : "Додај или уклони омиљене", "Manage tags" : "Управљање ознакама", "Selection" : "Избор", "Select all files" : "Изабери све фајлове", "Deselect all" : "Поништи цео избор", + "Select or deselect" : "Изабери или уклони избор", + "Select a range" : "Изабери опсег", "Navigation" : "Навигација", + "Go to parent folder" : "Иди на фолдер родитељ", + "Go to file above" : "Иди на фајл изнад", + "Go to file below" : "Иди на фајл испод", + "Go left in grid" : "Иди на лево у мрежи", + "Go right in grid" : "Иди на десно у мреж", "View" : "Погледај", "Toggle grid view" : "Укључи/искључи приказ мреже", + "Open file sidebar" : "Отвори бочну траку за фајл", "Show those shortcuts" : "Прикажи те пречице", "You" : "Ви", "Shared multiple times with different people" : "Дељено више пута са разним људима", @@ -298,6 +321,7 @@ "Templates" : "Шаблони", "New template folder" : "Нови фолдер шаблона", "In folder" : "У фолдеру", + "Search in all files" : "Претражи у свим фајловима", "Search in folder: {folder}" : "Претрага у фолдеру: {folder}", "One of the dropped files could not be processed" : "Један од упуштених фајлова не може да се обради", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Ваш интернет прегледач не подржава Filesystem API. Нећете моћи да отпремате директоријуме", @@ -316,6 +340,7 @@ "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Назив „{newName}” се већ користи у директоријуму „{dir}”. Молимо вас да изаберете неко друго име.", "Could not rename \"{oldName}\"" : "Не може да се промени име фајла „{oldName}”", "This operation is forbidden" : "Ова радња је забрањена", + "This folder is unavailable, please try again later or contact the administration" : "Овај фолдер није доступан, молимо вас да покушате касније или да контактирате администрацију", "Storage is temporarily not available" : "Складиште привремено није доступно", "Unexpected error: {error}" : "Неочекивана грешка: {error}", "_%n file_::_%n files_" : ["%n фајл","%n фајла","%n фајлова"], diff --git a/apps/files/lib/Command/Object/Multi/Rename.php b/apps/files/lib/Command/Object/Multi/Rename.php new file mode 100644 index 00000000000..562c68eb07f --- /dev/null +++ b/apps/files/lib/Command/Object/Multi/Rename.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Command\Object\Multi; + +use OC\Core\Command\Base; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; +use OCP\IConfig; +use OCP\IDBConnection; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class Rename extends Base { + public function __construct( + private readonly IDBConnection $connection, + private readonly PrimaryObjectStoreConfig $objectStoreConfig, + private readonly IConfig $config, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('files:object:multi:rename-config') + ->setDescription('Rename an object store configuration and move all users over to the new configuration,') + ->addArgument('source', InputArgument::REQUIRED, 'Object store configuration to rename') + ->addArgument('target', InputArgument::REQUIRED, 'New name for the object store configuration'); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $source = $input->getArgument('source'); + $target = $input->getArgument('target'); + + $configs = $this->objectStoreConfig->getObjectStoreConfigs(); + if (!isset($configs[$source])) { + $output->writeln('<error>Unknown object store configuration: ' . $source . '</error>'); + return 1; + } + + if ($source === 'root') { + $output->writeln('<error>Renaming the root configuration is not supported.</error>'); + return 1; + } + + if ($source === 'default') { + $output->writeln('<error>Renaming the default configuration is not supported.</error>'); + return 1; + } + + if (!isset($configs[$target])) { + $output->writeln('<comment>Target object store configuration ' . $target . ' doesn\'t exist yet.</comment>'); + $output->writeln('The target configuration can be created automatically.'); + $output->writeln('However, as this depends on modifying the config.php, this only works as long as the instance runs on a single node or all nodes in a clustered setup have a shared config file (such as from a shared network mount).'); + $output->writeln('If the different nodes have a separate copy of the config.php file, the automatic object store configuration creation will lead to the configuration going out of sync.'); + $output->writeln('If these requirements are not met, you can manually create the target object store configuration in each node\'s configuration before running the command.'); + $output->writeln(''); + $output->writeln('<error>Failure to check these requirements will lead to data loss for users.</error>'); + + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Automatically create target object store configuration? [y/N] ', false); + if ($helper->ask($input, $output, $question)) { + $configs[$target] = $configs[$source]; + + // update all aliases + foreach ($configs as &$config) { + if ($config === $source) { + $config = $target; + } + } + $this->config->setSystemValue('objectstore', $configs); + } else { + return 0; + } + } elseif (($configs[$source] !== $configs[$target]) || $configs[$source] !== $target) { + $output->writeln('<error>Source and target configuration differ.</error>'); + $output->writeln(''); + $output->writeln('To ensure proper migration of users, the source and target configuration must be the same to ensure that the objects for the moved users exist on the target configuration.'); + $output->writeln('The usual migration process consists of creating a clone of the old configuration, moving the users from the old configuration to the new one, and then adjust the old configuration that is longer used.'); + return 1; + } + + $query = $this->connection->getQueryBuilder(); + $query->update('preferences') + ->set('configvalue', $query->createNamedParameter($target)) + ->where($query->expr()->eq('appid', $query->createNamedParameter('homeobjectstore'))) + ->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('objectstore'))) + ->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter($source))); + $count = $query->executeStatement(); + + if ($count > 0) { + $output->writeln('Moved <info>' . $count . '</info> users'); + } else { + $output->writeln('No users moved'); + } + + return 0; + } +} diff --git a/apps/files/lib/Command/Object/Multi/Users.php b/apps/files/lib/Command/Object/Multi/Users.php new file mode 100644 index 00000000000..e8f7d012641 --- /dev/null +++ b/apps/files/lib/Command/Object/Multi/Users.php @@ -0,0 +1,98 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Command\Object\Multi; + +use OC\Core\Command\Base; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Users extends Base { + public function __construct( + private readonly IUserManager $userManager, + private readonly PrimaryObjectStoreConfig $objectStoreConfig, + private readonly IConfig $config, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('files:object:multi:users') + ->setDescription('Get the mapping between users and object store buckets') + ->addOption('bucket', 'b', InputOption::VALUE_REQUIRED, 'Only list users using the specified bucket') + ->addOption('object-store', 'o', InputOption::VALUE_REQUIRED, 'Only list users using the specified object store configuration') + ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Only show the mapping for the specified user, ignores all other options'); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + if ($userId = $input->getOption('user')) { + $user = $this->userManager->get($userId); + if (!$user) { + $output->writeln("<error>User $userId not found</error>"); + return 1; + } + $users = new \ArrayIterator([$user]); + } else { + $bucket = (string)$input->getOption('bucket'); + $objectStore = (string)$input->getOption('object-store'); + if ($bucket !== '' && $objectStore === '') { + $users = $this->getUsers($this->config->getUsersForUserValue('homeobjectstore', 'bucket', $bucket)); + } elseif ($bucket === '' && $objectStore !== '') { + $users = $this->getUsers($this->config->getUsersForUserValue('homeobjectstore', 'objectstore', $objectStore)); + } elseif ($bucket) { + $users = $this->getUsers(array_intersect( + $this->config->getUsersForUserValue('homeobjectstore', 'bucket', $bucket), + $this->config->getUsersForUserValue('homeobjectstore', 'objectstore', $objectStore) + )); + } else { + $users = $this->userManager->getSeenUsers(); + } + } + + $this->writeStreamingTableInOutputFormat($input, $output, $this->infoForUsers($users), 100); + return 0; + } + + /** + * @param string[] $userIds + * @return \Iterator<IUser> + */ + private function getUsers(array $userIds): \Iterator { + foreach ($userIds as $userId) { + $user = $this->userManager->get($userId); + if ($user) { + yield $user; + } + } + } + + /** + * @param \Iterator<IUser> $users + * @return \Iterator<array> + */ + private function infoForUsers(\Iterator $users): \Iterator { + foreach ($users as $user) { + yield $this->infoForUser($user); + } + } + + private function infoForUser(IUser $user): array { + return [ + 'user' => $user->getUID(), + 'object-store' => $this->objectStoreConfig->getObjectStoreForUser($user), + 'bucket' => $this->objectStoreConfig->getSetBucketForUser($user) ?? 'unset', + ]; + } +} diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php index 84c99f32109..afef5d2093d 100644 --- a/apps/files/lib/Service/OwnershipTransferService.php +++ b/apps/files/lib/Service/OwnershipTransferService.php @@ -333,15 +333,10 @@ class OwnershipTransferService { if ($path !== "$sourceUid/files") { $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $normalizedPath) { try { - $relativePath = $view->getPath($share->getNodeId()); - $singleFileTranfer = $view->is_file($normalizedPath); - if ($singleFileTranfer) { - return Filesystem::normalizePath($relativePath) === $normalizedPath; - } + $sourceNode = $share->getNode(); + $relativePath = $view->getRelativePath($sourceNode->getPath()); - return mb_strpos( - Filesystem::normalizePath($relativePath . '/', false), - $normalizedPath . '/') === 0; + return str_starts_with($relativePath . '/', $normalizedPath . '/'); } catch (Exception $e) { return false; } @@ -357,7 +352,7 @@ class OwnershipTransferService { return array_values(array_filter(array_map(function (IShare $share) use ($view, $normalizedPath, $output, $sourceUid) { try { - $nodePath = $view->getPath($share->getNodeId()); + $nodePath = $view->getRelativePath($share->getNode()->getPath()); } catch (NotFoundException $e) { $output->writeln("<error>Failed to find path for shared file {$share->getNodeId()} for user $sourceUid, skipping</error>"); return null; diff --git a/apps/files_reminders/l10n/es.js b/apps/files_reminders/l10n/es.js index 7d1d3b04b0f..3161133516a 100644 --- a/apps/files_reminders/l10n/es.js +++ b/apps/files_reminders/l10n/es.js @@ -5,27 +5,35 @@ OC.L10N.register( "Reminder for {name}" : "Recordatorio para {name}", "View file" : "Ver archivo", "View folder" : "Ver carpeta", + "Files reminder" : "Recordatorios de archivo", + "The \"files_reminders\" app can work properly." : "La app \"files_reminders\" puede trabajar de forma apropiada.", + "The \"files_reminders\" app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "La app \"files_reminders\" requiere la app de notificaciones para trabajar de forma apropiada. Debería o bien habilitar las notificaciones o deshabilitar files_reminder.", "Set file reminders" : "Establecer recordatorios de archivo", + "**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Recordatorios de archivo**\n\nEstablecer recordatorios de archivo.\n\nNota: para usar la app de `Recordatorios de archivo`, asegúrese de que la app de `Notificaciones` está instalada y habilitada. La app de `Notificaciones` provee las APIs necesarias para que la app de `Recordatorios de archivo` funcione correctamente.", "Set reminder for \"{fileName}\"" : "Establecer recordatorio para \"{fileName}\"", + "Reminder at custom date & time" : "Recordatorio en una fecha y hora personalizada", "Clear reminder" : "Borrar recordatorio", "Please choose a valid date & time" : "Por favor, escoja una fecha y hora válidas", "Reminder set for \"{fileName}\"" : "Se estableció recordatorio para \"{fileName}\"", - "Failed to set reminder" : "No se pudo configurar el recordatorio", + "Failed to set reminder" : "No se pudo establecer el recordatorio", "Reminder cleared for \"{fileName}\"" : "Se limpió el recordatorio para \"{fileName}\"", - "Failed to clear reminder" : "Fallo al quitar el recordatorio", + "Failed to clear reminder" : "Fallo al borrar el recordatorio", "We will remind you of this file" : "Le recordaremos de este archivo", "Cancel" : "Cancelar", - "Set reminder" : "Enviar recordatorio", + "Set reminder" : "Establecer recordatorio", "Reminder set" : "Recordatorio establecido", + "Custom reminder" : "Recordatorio personalizado", "Later today" : "Más tarde hoy", - "Set reminder for later today" : "Configurar recordatorio para hoy, más tarde", + "Set reminder for later today" : "Establecer recordatorio para hoy, más tarde", "Tomorrow" : "Mañana", - "Set reminder for tomorrow" : "Configurar recordatorio para mañana", + "Set reminder for tomorrow" : "Establecer recordatorio para mañana", "This weekend" : "Este fin de semana", - "Set reminder for this weekend" : "Configurar recordatorio para este fin de semana", - "Next week" : "Semana siguiente", - "Set reminder for next week" : "Configurar recordatorio para la semana que viene", + "Set reminder for this weekend" : "Establecer recordatorio para este fin de semana", + "Next week" : "Próxima semana", + "Set reminder for next week" : "Establecer recordatorio para la próxima semana", + "This files_reminder can work properly." : "Este files_reminder puede trabajar de forma apropiada.", + "The files_reminder app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "La app \"files_reminders\" requiere la app de notificaciones para trabajar de forma apropiada. Debería o bien habilitar las notificaciones o deshabilitar files_reminder.", "Set reminder at custom date & time" : "Establecer recordatorio a una fecha y hora personalizada", - "Set custom reminder" : "Configurar recordatorio personalizado" + "Set custom reminder" : "Establecer recordatorio personalizado" }, "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/apps/files_reminders/l10n/es.json b/apps/files_reminders/l10n/es.json index 509b50ff424..ca3bc8ce1cd 100644 --- a/apps/files_reminders/l10n/es.json +++ b/apps/files_reminders/l10n/es.json @@ -3,27 +3,35 @@ "Reminder for {name}" : "Recordatorio para {name}", "View file" : "Ver archivo", "View folder" : "Ver carpeta", + "Files reminder" : "Recordatorios de archivo", + "The \"files_reminders\" app can work properly." : "La app \"files_reminders\" puede trabajar de forma apropiada.", + "The \"files_reminders\" app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "La app \"files_reminders\" requiere la app de notificaciones para trabajar de forma apropiada. Debería o bien habilitar las notificaciones o deshabilitar files_reminder.", "Set file reminders" : "Establecer recordatorios de archivo", + "**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Recordatorios de archivo**\n\nEstablecer recordatorios de archivo.\n\nNota: para usar la app de `Recordatorios de archivo`, asegúrese de que la app de `Notificaciones` está instalada y habilitada. La app de `Notificaciones` provee las APIs necesarias para que la app de `Recordatorios de archivo` funcione correctamente.", "Set reminder for \"{fileName}\"" : "Establecer recordatorio para \"{fileName}\"", + "Reminder at custom date & time" : "Recordatorio en una fecha y hora personalizada", "Clear reminder" : "Borrar recordatorio", "Please choose a valid date & time" : "Por favor, escoja una fecha y hora válidas", "Reminder set for \"{fileName}\"" : "Se estableció recordatorio para \"{fileName}\"", - "Failed to set reminder" : "No se pudo configurar el recordatorio", + "Failed to set reminder" : "No se pudo establecer el recordatorio", "Reminder cleared for \"{fileName}\"" : "Se limpió el recordatorio para \"{fileName}\"", - "Failed to clear reminder" : "Fallo al quitar el recordatorio", + "Failed to clear reminder" : "Fallo al borrar el recordatorio", "We will remind you of this file" : "Le recordaremos de este archivo", "Cancel" : "Cancelar", - "Set reminder" : "Enviar recordatorio", + "Set reminder" : "Establecer recordatorio", "Reminder set" : "Recordatorio establecido", + "Custom reminder" : "Recordatorio personalizado", "Later today" : "Más tarde hoy", - "Set reminder for later today" : "Configurar recordatorio para hoy, más tarde", + "Set reminder for later today" : "Establecer recordatorio para hoy, más tarde", "Tomorrow" : "Mañana", - "Set reminder for tomorrow" : "Configurar recordatorio para mañana", + "Set reminder for tomorrow" : "Establecer recordatorio para mañana", "This weekend" : "Este fin de semana", - "Set reminder for this weekend" : "Configurar recordatorio para este fin de semana", - "Next week" : "Semana siguiente", - "Set reminder for next week" : "Configurar recordatorio para la semana que viene", + "Set reminder for this weekend" : "Establecer recordatorio para este fin de semana", + "Next week" : "Próxima semana", + "Set reminder for next week" : "Establecer recordatorio para la próxima semana", + "This files_reminder can work properly." : "Este files_reminder puede trabajar de forma apropiada.", + "The files_reminder app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "La app \"files_reminders\" requiere la app de notificaciones para trabajar de forma apropiada. Debería o bien habilitar las notificaciones o deshabilitar files_reminder.", "Set reminder at custom date & time" : "Establecer recordatorio a una fecha y hora personalizada", - "Set custom reminder" : "Configurar recordatorio personalizado" + "Set custom reminder" : "Establecer recordatorio personalizado" },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" }
\ No newline at end of file diff --git a/apps/files_reminders/l10n/pt_BR.js b/apps/files_reminders/l10n/pt_BR.js index ac662d677af..3ed3e3e2949 100644 --- a/apps/files_reminders/l10n/pt_BR.js +++ b/apps/files_reminders/l10n/pt_BR.js @@ -11,6 +11,7 @@ OC.L10N.register( "Set file reminders" : "Defina lembretes de arquivo", "**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Lembretes de arquivos** \n\nDefina lembretes de arquivos. \n\nObservação: para usar o aplicativo `Lembretes de arquivos`, certifique-se de que o aplicativo `Notificações` esteja instalado e habilitado. O aplicativo `Notificações` fornece as APIs necessárias para que o aplicativo `Lembretes de arquivos` funcione corretamente.", "Set reminder for \"{fileName}\"" : "Definir lembrete para \"{fileName}\"", + "Reminder at custom date & time" : "Lembrete em data e hora personalizadas", "Clear reminder" : "Limpar lembrete", "Please choose a valid date & time" : "Por favor escolha uma data & hora válida.", "Reminder set for \"{fileName}\"" : "Lembrete definido para \"{fileName}\"", @@ -21,6 +22,7 @@ OC.L10N.register( "Cancel" : "Cancelar", "Set reminder" : "Definir lembrete", "Reminder set" : "Lembrete definido", + "Custom reminder" : "Lembrete personalizado", "Later today" : "Hoje mais tarde", "Set reminder for later today" : "Definir lembrete para hoje mais tarde", "Tomorrow" : "Amanhã", diff --git a/apps/files_reminders/l10n/pt_BR.json b/apps/files_reminders/l10n/pt_BR.json index d4a04ec5eec..e5be7701fae 100644 --- a/apps/files_reminders/l10n/pt_BR.json +++ b/apps/files_reminders/l10n/pt_BR.json @@ -9,6 +9,7 @@ "Set file reminders" : "Defina lembretes de arquivo", "**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Lembretes de arquivos** \n\nDefina lembretes de arquivos. \n\nObservação: para usar o aplicativo `Lembretes de arquivos`, certifique-se de que o aplicativo `Notificações` esteja instalado e habilitado. O aplicativo `Notificações` fornece as APIs necessárias para que o aplicativo `Lembretes de arquivos` funcione corretamente.", "Set reminder for \"{fileName}\"" : "Definir lembrete para \"{fileName}\"", + "Reminder at custom date & time" : "Lembrete em data e hora personalizadas", "Clear reminder" : "Limpar lembrete", "Please choose a valid date & time" : "Por favor escolha uma data & hora válida.", "Reminder set for \"{fileName}\"" : "Lembrete definido para \"{fileName}\"", @@ -19,6 +20,7 @@ "Cancel" : "Cancelar", "Set reminder" : "Definir lembrete", "Reminder set" : "Lembrete definido", + "Custom reminder" : "Lembrete personalizado", "Later today" : "Hoje mais tarde", "Set reminder for later today" : "Definir lembrete para hoje mais tarde", "Tomorrow" : "Amanhã", diff --git a/apps/files_reminders/lib/Dav/PropFindPlugin.php b/apps/files_reminders/lib/Dav/PropFindPlugin.php index 014e636eb2d..7fa45a4b854 100644 --- a/apps/files_reminders/lib/Dav/PropFindPlugin.php +++ b/apps/files_reminders/lib/Dav/PropFindPlugin.php @@ -16,6 +16,7 @@ use OCA\FilesReminders\Service\ReminderService; use OCP\Files\Folder; use OCP\IUser; use OCP\IUserSession; +use Sabre\DAV\ICollection; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -32,9 +33,22 @@ class PropFindPlugin extends ServerPlugin { } public function initialize(Server $server): void { + $server->on('preloadCollection', $this->preloadCollection(...)); $server->on('propFind', [$this, 'propFind']); } + private function preloadCollection( + PropFind $propFind, + ICollection $collection, + ): void { + if ($collection instanceof Directory && $propFind->getStatus( + static::REMINDER_DUE_DATE_PROPERTY + ) !== null) { + $folder = $collection->getNode(); + $this->cacheFolder($folder); + } + } + public function propFind(PropFind $propFind, INode $node) { if (!in_array(static::REMINDER_DUE_DATE_PROPERTY, $propFind->getRequestedProperties())) { return; @@ -44,15 +58,6 @@ class PropFindPlugin extends ServerPlugin { return; } - if ( - $node instanceof Directory - && $propFind->getDepth() > 0 - && $propFind->getStatus(static::REMINDER_DUE_DATE_PROPERTY) !== null - ) { - $folder = $node->getNode(); - $this->cacheFolder($folder); - } - $propFind->handle( static::REMINDER_DUE_DATE_PROPERTY, function () use ($node) { diff --git a/apps/files_sharing/l10n/de_DE.js b/apps/files_sharing/l10n/de_DE.js index a3507239899..a7dc879a8f0 100644 --- a/apps/files_sharing/l10n/de_DE.js +++ b/apps/files_sharing/l10n/de_DE.js @@ -304,13 +304,13 @@ OC.L10N.register( "Link shares" : "Freigaben teilen", "Shares" : "Freigaben", "Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb Ihrer Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.", - "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können Nextcloud-Konten auch auf anderen Instanzen mithilfe ihrer föderierten Cloud-ID teilen.", + "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können Nextcloud-Konten auch auf anderen Instanzen mithilfe ihrer Federated-Cloud-ID teilen.", "Shares from apps or other sources which are not included in internal or external shares." : "Freigaben aus Apps oder anderen Quellen, die nicht in internen oder externen Freigaben enthalten sind.", - "Type names, teams, federated cloud IDs" : "Namen, Teams oder Federierte Cloud-IDs eingeben", - "Type names or teams" : "Namen oder Federierte Cloud-IDs eingeben", - "Type a federated cloud ID" : "Eine Federierte Cloud-ID eingeben", - "Type an email" : "Eine E-Mailadresse eingeben", - "Type an email or federated cloud ID" : "Eine E-Mailadresse oder eine Federierte Cloud-ID eingeben", + "Type names, teams, federated cloud IDs" : "Namen, Teams oder Federated-Cloud-IDs eingeben", + "Type names or teams" : "Namen oder Teams eingeben", + "Type a federated cloud ID" : "Eine Federated-Cloud-ID eingeben", + "Type an email" : "Eine E-Mail-Adresse eingeben", + "Type an email or federated cloud ID" : "Eine E-Mail-Adresse oder eine Federated-Cloud-ID eingeben", "Unable to load the shares list" : "Liste der Freigaben kann nicht geladen werden", "Expires {relativetime}" : "Läuft {relativetime} ab", "this share just expired." : "Diese Freigabe ist gerade abgelaufen.", diff --git a/apps/files_sharing/l10n/de_DE.json b/apps/files_sharing/l10n/de_DE.json index 41d191e8166..7e4298f986a 100644 --- a/apps/files_sharing/l10n/de_DE.json +++ b/apps/files_sharing/l10n/de_DE.json @@ -302,13 +302,13 @@ "Link shares" : "Freigaben teilen", "Shares" : "Freigaben", "Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb Ihrer Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.", - "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können Nextcloud-Konten auch auf anderen Instanzen mithilfe ihrer föderierten Cloud-ID teilen.", + "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können Nextcloud-Konten auch auf anderen Instanzen mithilfe ihrer Federated-Cloud-ID teilen.", "Shares from apps or other sources which are not included in internal or external shares." : "Freigaben aus Apps oder anderen Quellen, die nicht in internen oder externen Freigaben enthalten sind.", - "Type names, teams, federated cloud IDs" : "Namen, Teams oder Federierte Cloud-IDs eingeben", - "Type names or teams" : "Namen oder Federierte Cloud-IDs eingeben", - "Type a federated cloud ID" : "Eine Federierte Cloud-ID eingeben", - "Type an email" : "Eine E-Mailadresse eingeben", - "Type an email or federated cloud ID" : "Eine E-Mailadresse oder eine Federierte Cloud-ID eingeben", + "Type names, teams, federated cloud IDs" : "Namen, Teams oder Federated-Cloud-IDs eingeben", + "Type names or teams" : "Namen oder Teams eingeben", + "Type a federated cloud ID" : "Eine Federated-Cloud-ID eingeben", + "Type an email" : "Eine E-Mail-Adresse eingeben", + "Type an email or federated cloud ID" : "Eine E-Mail-Adresse oder eine Federated-Cloud-ID eingeben", "Unable to load the shares list" : "Liste der Freigaben kann nicht geladen werden", "Expires {relativetime}" : "Läuft {relativetime} ab", "this share just expired." : "Diese Freigabe ist gerade abgelaufen.", diff --git a/apps/files_sharing/l10n/pt_BR.js b/apps/files_sharing/l10n/pt_BR.js index 4f9db865df5..c308cfdb122 100644 --- a/apps/files_sharing/l10n/pt_BR.js +++ b/apps/files_sharing/l10n/pt_BR.js @@ -202,6 +202,7 @@ OC.L10N.register( "Unshare" : "Descompartilhar", "Cannot copy, please copy the link manually" : "Não é possível copiar, copie o link manualmente", "Copy internal link" : "Copiar link interno", + "For people who already have access" : "Para pessoas que já têm acesso", "Internal link" : "Link interno", "{shareWith} by {initiator}" : "{shareWith} por {initiator}", "Shared via link by {initiator}" : "Compartilhado via link por {initiator}", @@ -212,6 +213,7 @@ OC.L10N.register( "Share link ({index})" : "Link de compartilhamento ({index})", "Create public link" : "Criar link público", "Actions for \"{title}\"" : "Ações para \"{title}\"", + "Copy public link of \"{title}\"" : "Copiar link público de \"{title}\"", "Error, please enter proper password and/or expiration date" : "Erro, digite a senha correta e/ou a data de validade", "Link share created" : "Compartilhamento por link criado", "Error while creating the share" : "Erro ao criar o compartilhamento", @@ -256,6 +258,7 @@ OC.L10N.register( "Successfully uploaded files" : "Arquivos carregados com sucesso", "View terms of service" : "Ver os termos de serviço", "Terms of service" : "Termos de serviço", + "Share with {user}" : "Compartilhe com {user}", "Share with email {email}" : "Compartilhar com e-mail {email}", "Share with group" : "Compartilhar com grupo", "Share in conversation" : "Compartilhar na conversa", @@ -300,6 +303,14 @@ OC.L10N.register( "Unable to fetch inherited shares" : "Não foi possível buscar compartilhamentos herdados", "Link shares" : "Compartilhamentos por link", "Shares" : "Compartilhamentos", + "Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Compartilhe arquivos dentro da sua organização. Os destinatários que já conseguem visualizar o arquivo também podem usar este link para facilitar o acesso.", + "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Compartilhe arquivos com outras pessoas fora da sua organização por meio de links públicos e endereços de e-mail. Você também pode compartilhar com contas Nextcloud em outras instâncias usando o ID de nuvem federado delas.", + "Shares from apps or other sources which are not included in internal or external shares." : "Compartilhamentos de aplicativos ou outras fontes que não estão incluídos em compartilhamentos internos ou externos.", + "Type names, teams, federated cloud IDs" : "Digite nomes, equipes, IDs de nuvem federada", + "Type names or teams" : "Digite nomes ou equipes", + "Type a federated cloud ID" : "Digite um ID de nuvem federada", + "Type an email" : "Digite um e-mail", + "Type an email or federated cloud ID" : "Digite um e-mail ou ID de nuvem federada", "Unable to load the shares list" : "Não foi possível carregar a lista de compartilhamentos", "Expires {relativetime}" : "Expira {relativetime}", "this share just expired." : "esse compartilhamento acabou de expirar.", diff --git a/apps/files_sharing/l10n/pt_BR.json b/apps/files_sharing/l10n/pt_BR.json index 1d6f17a194f..27b631d6137 100644 --- a/apps/files_sharing/l10n/pt_BR.json +++ b/apps/files_sharing/l10n/pt_BR.json @@ -200,6 +200,7 @@ "Unshare" : "Descompartilhar", "Cannot copy, please copy the link manually" : "Não é possível copiar, copie o link manualmente", "Copy internal link" : "Copiar link interno", + "For people who already have access" : "Para pessoas que já têm acesso", "Internal link" : "Link interno", "{shareWith} by {initiator}" : "{shareWith} por {initiator}", "Shared via link by {initiator}" : "Compartilhado via link por {initiator}", @@ -210,6 +211,7 @@ "Share link ({index})" : "Link de compartilhamento ({index})", "Create public link" : "Criar link público", "Actions for \"{title}\"" : "Ações para \"{title}\"", + "Copy public link of \"{title}\"" : "Copiar link público de \"{title}\"", "Error, please enter proper password and/or expiration date" : "Erro, digite a senha correta e/ou a data de validade", "Link share created" : "Compartilhamento por link criado", "Error while creating the share" : "Erro ao criar o compartilhamento", @@ -254,6 +256,7 @@ "Successfully uploaded files" : "Arquivos carregados com sucesso", "View terms of service" : "Ver os termos de serviço", "Terms of service" : "Termos de serviço", + "Share with {user}" : "Compartilhe com {user}", "Share with email {email}" : "Compartilhar com e-mail {email}", "Share with group" : "Compartilhar com grupo", "Share in conversation" : "Compartilhar na conversa", @@ -298,6 +301,14 @@ "Unable to fetch inherited shares" : "Não foi possível buscar compartilhamentos herdados", "Link shares" : "Compartilhamentos por link", "Shares" : "Compartilhamentos", + "Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Compartilhe arquivos dentro da sua organização. Os destinatários que já conseguem visualizar o arquivo também podem usar este link para facilitar o acesso.", + "Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID." : "Compartilhe arquivos com outras pessoas fora da sua organização por meio de links públicos e endereços de e-mail. Você também pode compartilhar com contas Nextcloud em outras instâncias usando o ID de nuvem federado delas.", + "Shares from apps or other sources which are not included in internal or external shares." : "Compartilhamentos de aplicativos ou outras fontes que não estão incluídos em compartilhamentos internos ou externos.", + "Type names, teams, federated cloud IDs" : "Digite nomes, equipes, IDs de nuvem federada", + "Type names or teams" : "Digite nomes ou equipes", + "Type a federated cloud ID" : "Digite um ID de nuvem federada", + "Type an email" : "Digite um e-mail", + "Type an email or federated cloud ID" : "Digite um e-mail ou ID de nuvem federada", "Unable to load the shares list" : "Não foi possível carregar a lista de compartilhamentos", "Expires {relativetime}" : "Expira {relativetime}", "this share just expired." : "esse compartilhamento acabou de expirar.", diff --git a/apps/files_sharing/l10n/sr.js b/apps/files_sharing/l10n/sr.js index 894bb17b920..a32961e5ab2 100644 --- a/apps/files_sharing/l10n/sr.js +++ b/apps/files_sharing/l10n/sr.js @@ -202,6 +202,7 @@ OC.L10N.register( "Unshare" : "Укини дељење", "Cannot copy, please copy the link manually" : "Не могу да копирам, копирајте везу ручно", "Copy internal link" : "Копирај интерну везу", + "For people who already have access" : "Особе које већ имају приступ", "Internal link" : "Интерна веза", "{shareWith} by {initiator}" : "{shareWith} od {initiator}", "Shared via link by {initiator}" : "{initiator} поделио преко везе", diff --git a/apps/files_sharing/l10n/sr.json b/apps/files_sharing/l10n/sr.json index 4a59406e5ac..af0e916dc89 100644 --- a/apps/files_sharing/l10n/sr.json +++ b/apps/files_sharing/l10n/sr.json @@ -200,6 +200,7 @@ "Unshare" : "Укини дељење", "Cannot copy, please copy the link manually" : "Не могу да копирам, копирајте везу ручно", "Copy internal link" : "Копирај интерну везу", + "For people who already have access" : "Особе које већ имају приступ", "Internal link" : "Интерна веза", "{shareWith} by {initiator}" : "{shareWith} od {initiator}", "Shared via link by {initiator}" : "{initiator} поделио преко везе", diff --git a/apps/files_sharing/lib/Capabilities.php b/apps/files_sharing/lib/Capabilities.php index cbb9b5cd2f2..06aa1271c8f 100644 --- a/apps/files_sharing/lib/Capabilities.php +++ b/apps/files_sharing/lib/Capabilities.php @@ -7,9 +7,11 @@ */ namespace OCA\Files_Sharing; +use OC\Core\AppInfo\ConfigLexicon; use OCP\App\IAppManager; use OCP\Capabilities\ICapability; use OCP\Constants; +use OCP\IAppConfig; use OCP\IConfig; use OCP\Share\IManager; @@ -21,6 +23,7 @@ use OCP\Share\IManager; class Capabilities implements ICapability { public function __construct( private IConfig $config, + private readonly IAppConfig $appConfig, private IManager $shareManager, private IAppManager $appManager, ) { @@ -111,7 +114,7 @@ class Capabilities implements ICapability { if ($public['password']['enforced']) { $public['password']['askForOptionalPassword'] = false; } else { - $public['password']['askForOptionalPassword'] = ($this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no') === 'yes'); + $public['password']['askForOptionalPassword'] = $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_DEFAULT); } $public['expire_date'] = []; diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index 7a0b1f135a6..b7b0582493e 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -46,13 +46,14 @@ class MountProvider implements IMountProvider { * @return IMountPoint[] */ public function getMountsForUser(IUser $user, IStorageFactory $loader) { - $shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_USER, null, -1); - $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1)); - $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1)); - $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_ROOM, null, -1)); - $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_DECK, null, -1)); - $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_SCIENCEMESH, null, -1)); - + $shares = array_merge( + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_USER, null, -1), + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1), + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1), + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_ROOM, null, -1), + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_DECK, null, -1), + $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_SCIENCEMESH, null, -1), + ); // filter out excluded shares and group shares that includes self $shares = array_filter($shares, function (IShare $share) use ($user) { diff --git a/apps/files_sharing/tests/CapabilitiesTest.php b/apps/files_sharing/tests/CapabilitiesTest.php index 2fe221703a5..9a076d7a171 100644 --- a/apps/files_sharing/tests/CapabilitiesTest.php +++ b/apps/files_sharing/tests/CapabilitiesTest.php @@ -56,11 +56,30 @@ class CapabilitiesTest extends \Test\TestCase { * @param (string[])[] $map Map of arguments to return types for the getAppValue function in the mock * @return string[] */ - private function getResults(array $map, bool $federationEnabled = true) { + private function getResults(array $map, array $typedMap = [], bool $federationEnabled = true) { $config = $this->getMockBuilder(IConfig::class)->disableOriginalConstructor()->getMock(); $appManager = $this->getMockBuilder(IAppManager::class)->disableOriginalConstructor()->getMock(); $config->method('getAppValue')->willReturnMap($map); $appManager->method('isEnabledForAnyone')->with('federation')->willReturn($federationEnabled); + + if (empty($typedMap)) { + $appConfig = $this->createMock(IAppConfig::class); + } else { + // hack to help transition from old IConfig to new IAppConfig + $appConfig = $this->getMockBuilder(IAppConfig::class)->disableOriginalConstructor()->getMock(); + $appConfig->expects($this->any())->method('getValueBool')->willReturnCallback(function (...$args) use ($typedMap): bool { + foreach ($typedMap as $entry) { + if ($entry[0] !== $args[0] || $entry[1] !== $args[1]) { + continue; + } + + return $entry[2]; + } + + return false; + }); + } + $shareManager = new Manager( $this->createMock(LoggerInterface::class), $config, @@ -80,9 +99,10 @@ class CapabilitiesTest extends \Test\TestCase { $this->createMock(KnownUserService::class), $this->createMock(ShareDisableChecker::class), $this->createMock(IDateTimeZone::class), - $this->createMock(IAppConfig::class), + $appConfig, ); - $cap = new Capabilities($config, $shareManager, $appManager); + + $cap = new Capabilities($config, $appConfig, $shareManager, $appManager); $result = $this->getFilesSharingPart($cap->getCapabilities()); return $result; } @@ -135,9 +155,11 @@ class CapabilitiesTest extends \Test\TestCase { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], - ['core', 'shareapi_enforce_links_password', 'no', 'yes'], ]; - $result = $this->getResults($map); + $typedMap = [ + ['core', 'shareapi_enforce_links_password', true], + ]; + $result = $this->getResults($map, $typedMap); $this->assertArrayHasKey('password', $result['public']); $this->assertArrayHasKey('enforced', $result['public']['password']); $this->assertTrue($result['public']['password']['enforced']); @@ -328,7 +350,7 @@ class CapabilitiesTest extends \Test\TestCase { } public function testFederatedSharingDisabled(): void { - $result = $this->getResults([], false); + $result = $this->getResults([], federationEnabled: false); $this->assertArrayHasKey('federation', $result); $this->assertFalse($result['federation']['incoming']); $this->assertFalse($result['federation']['outgoing']); diff --git a/apps/settings/l10n/ar.js b/apps/settings/l10n/ar.js index 2a282cc2633..cfbb4ccf8ef 100644 --- a/apps/settings/l10n/ar.js +++ b/apps/settings/l10n/ar.js @@ -409,6 +409,10 @@ OC.L10N.register( "Excluded groups" : "المجموعات المُستثناة", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "عندما يتم تحديد أو استبعاد المجموعات، فإنها تستخدم المنطق التالي لتحديد ما إذا كان الحساب قد تم فرض المصادقة الثنائية عليه: إذا لم يتم تحديد أي مجموعات، فسيتم تمكين المصادقة الثنائية للجميع باستثناء أعضاء المجموعات المستبعدة. إذا تم تحديد المجموعات، فسيتم تمكين المصادقة الثنائية 2FA لجميع أعضاء هذه المجموعات. إذا كان الحساب موجودًا في مجموعة محددة ومستبعدة، فستكون الأولوية للحساب المحدد و سيتم فرض المصادقة الثنائية.", "Save changes" : "حفظ التعديلات", + "Default" : "التلقائي", + "Registered Deploy daemons list" : "قائمة برامج النشر الخفية المسجلة", + "No Deploy daemons configured" : "لم تتم تهيئة أي برامج نشر خفية", + "Register a custom one or setup from available templates" : "قُم بتسجيل واحدة مخصصة أو قم بتجهيز واحدة باستعمال القوالب المتاحة", "Show details for {appName} app" : "عرض تفاصيل التطبيق {appName} ", "Update to {update}" : "التحديث إلى {update}", "Remove" : "حذف", diff --git a/apps/settings/l10n/ar.json b/apps/settings/l10n/ar.json index 47afaa0ad29..4d84eb0054b 100644 --- a/apps/settings/l10n/ar.json +++ b/apps/settings/l10n/ar.json @@ -407,6 +407,10 @@ "Excluded groups" : "المجموعات المُستثناة", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "عندما يتم تحديد أو استبعاد المجموعات، فإنها تستخدم المنطق التالي لتحديد ما إذا كان الحساب قد تم فرض المصادقة الثنائية عليه: إذا لم يتم تحديد أي مجموعات، فسيتم تمكين المصادقة الثنائية للجميع باستثناء أعضاء المجموعات المستبعدة. إذا تم تحديد المجموعات، فسيتم تمكين المصادقة الثنائية 2FA لجميع أعضاء هذه المجموعات. إذا كان الحساب موجودًا في مجموعة محددة ومستبعدة، فستكون الأولوية للحساب المحدد و سيتم فرض المصادقة الثنائية.", "Save changes" : "حفظ التعديلات", + "Default" : "التلقائي", + "Registered Deploy daemons list" : "قائمة برامج النشر الخفية المسجلة", + "No Deploy daemons configured" : "لم تتم تهيئة أي برامج نشر خفية", + "Register a custom one or setup from available templates" : "قُم بتسجيل واحدة مخصصة أو قم بتجهيز واحدة باستعمال القوالب المتاحة", "Show details for {appName} app" : "عرض تفاصيل التطبيق {appName} ", "Update to {update}" : "التحديث إلى {update}", "Remove" : "حذف", diff --git a/apps/settings/l10n/ast.js b/apps/settings/l10n/ast.js index cb957a8ce65..fc77b0d4a7e 100644 --- a/apps/settings/l10n/ast.js +++ b/apps/settings/l10n/ast.js @@ -220,6 +220,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "L'autenticación en dos pasos nun ye obligatoria pa los miembros de los grupos siguientes.", "Excluded groups" : "Grupos escluyíos", "Save changes" : "Guardar los cambeos", + "Default" : "Por defeutu", "Remove" : "Quitar", "Featured" : "Destacada", "Disable all" : "Desactivar too", diff --git a/apps/settings/l10n/ast.json b/apps/settings/l10n/ast.json index fcf2f77974a..aa5850dde17 100644 --- a/apps/settings/l10n/ast.json +++ b/apps/settings/l10n/ast.json @@ -218,6 +218,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "L'autenticación en dos pasos nun ye obligatoria pa los miembros de los grupos siguientes.", "Excluded groups" : "Grupos escluyíos", "Save changes" : "Guardar los cambeos", + "Default" : "Por defeutu", "Remove" : "Quitar", "Featured" : "Destacada", "Disable all" : "Desactivar too", diff --git a/apps/settings/l10n/bg.js b/apps/settings/l10n/bg.js index acab0d4a5c9..e6a9abf895c 100644 --- a/apps/settings/l10n/bg.js +++ b/apps/settings/l10n/bg.js @@ -167,6 +167,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Двустепенно удостоверяване не се прилага за членове на следните групи.", "Excluded groups" : "Изключени групи", "Save changes" : "Запиши промените", + "Default" : "По подразбиране", "Update to {update}" : "Актуализирай до {update}", "Remove" : "Премахване", "Featured" : "Препоръчани", diff --git a/apps/settings/l10n/bg.json b/apps/settings/l10n/bg.json index 63adacb540e..7dced04dee8 100644 --- a/apps/settings/l10n/bg.json +++ b/apps/settings/l10n/bg.json @@ -165,6 +165,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Двустепенно удостоверяване не се прилага за членове на следните групи.", "Excluded groups" : "Изключени групи", "Save changes" : "Запиши промените", + "Default" : "По подразбиране", "Update to {update}" : "Актуализирай до {update}", "Remove" : "Премахване", "Featured" : "Препоръчани", diff --git a/apps/settings/l10n/ca.js b/apps/settings/l10n/ca.js index 00a70f84877..419c3525723 100644 --- a/apps/settings/l10n/ca.js +++ b/apps/settings/l10n/ca.js @@ -407,6 +407,10 @@ OC.L10N.register( "Excluded groups" : "Grups exclosos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Quan se seleccionen/exclouen grups, utilitzen la lògica següent per determinar si un compte té 2FA aplicat: Si no se selecciona cap grup, 2FA s'habilita per a tothom excepte per als membres dels grups exclosos. Si se seleccionen grups, s'habilita 2FA per a tots els membres d'aquests. Si un compte es troba tant en un grup seleccionat com en un grup exclòs, el seleccionat té prioritat i s'aplica la 2FA.", "Save changes" : "Desa els canvis", + "Default" : "Per defecte", + "Registered Deploy daemons list" : "Llista de dimonis de desplegament registrats", + "No Deploy daemons configured" : "No s'ha configurat cap dimoni de desplegament", + "Register a custom one or setup from available templates" : "Registreu-ne un personalitzat o configureu-ne a partir de les plantilles disponibles", "Show details for {appName} app" : "Mostra els detalls de l'aplicació {appName}", "Update to {update}" : "Actualitza a {update}", "Remove" : "Suprimeix", diff --git a/apps/settings/l10n/ca.json b/apps/settings/l10n/ca.json index 65c3dcb9e2a..89c397c4530 100644 --- a/apps/settings/l10n/ca.json +++ b/apps/settings/l10n/ca.json @@ -405,6 +405,10 @@ "Excluded groups" : "Grups exclosos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Quan se seleccionen/exclouen grups, utilitzen la lògica següent per determinar si un compte té 2FA aplicat: Si no se selecciona cap grup, 2FA s'habilita per a tothom excepte per als membres dels grups exclosos. Si se seleccionen grups, s'habilita 2FA per a tots els membres d'aquests. Si un compte es troba tant en un grup seleccionat com en un grup exclòs, el seleccionat té prioritat i s'aplica la 2FA.", "Save changes" : "Desa els canvis", + "Default" : "Per defecte", + "Registered Deploy daemons list" : "Llista de dimonis de desplegament registrats", + "No Deploy daemons configured" : "No s'ha configurat cap dimoni de desplegament", + "Register a custom one or setup from available templates" : "Registreu-ne un personalitzat o configureu-ne a partir de les plantilles disponibles", "Show details for {appName} app" : "Mostra els detalls de l'aplicació {appName}", "Update to {update}" : "Actualitza a {update}", "Remove" : "Suprimeix", diff --git a/apps/settings/l10n/cs.js b/apps/settings/l10n/cs.js index 048a4b61c40..38f5233e1ed 100644 --- a/apps/settings/l10n/cs.js +++ b/apps/settings/l10n/cs.js @@ -418,6 +418,10 @@ OC.L10N.register( "Excluded groups" : "Vynechané skupiny", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Když jsou skupiny vybrány/vynechány, je pro zjišťování zda je účtu vynuceno dvoufázové (2FA) ověřování použita následující logika: Pokud nejsou vybrány žádné skupiny, je 2FA zapnuto pro všechny kromě členů vynechaných skupin. Pokud jsou nějaké skupiny vybrány, je 2FA zapnuto pro všechny jejich členy. Pokud je účet členem jak vybrané, tak vynechané skupiny, pak má ta vybraná přednost a 2FA je vynuceno.", "Save changes" : "Uložit změny", + "Default" : "Výchozí", + "Registered Deploy daemons list" : "Seznam zaregistrovaných procesů nasazování", + "No Deploy daemons configured" : "Nejsou nastavené žádné procesy služby nasazování", + "Register a custom one or setup from available templates" : "Zaregistrujte uživatelsky určené nebo nastavte z dostupných šablon", "Show details for {appName} app" : "Zobrazit podrobnosti o aplikaci {appName}", "Update to {update}" : "Aktualizovat na {update}", "Remove" : "Odstranit", diff --git a/apps/settings/l10n/cs.json b/apps/settings/l10n/cs.json index 7bf480438b9..2df57ae35e3 100644 --- a/apps/settings/l10n/cs.json +++ b/apps/settings/l10n/cs.json @@ -416,6 +416,10 @@ "Excluded groups" : "Vynechané skupiny", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Když jsou skupiny vybrány/vynechány, je pro zjišťování zda je účtu vynuceno dvoufázové (2FA) ověřování použita následující logika: Pokud nejsou vybrány žádné skupiny, je 2FA zapnuto pro všechny kromě členů vynechaných skupin. Pokud jsou nějaké skupiny vybrány, je 2FA zapnuto pro všechny jejich členy. Pokud je účet členem jak vybrané, tak vynechané skupiny, pak má ta vybraná přednost a 2FA je vynuceno.", "Save changes" : "Uložit změny", + "Default" : "Výchozí", + "Registered Deploy daemons list" : "Seznam zaregistrovaných procesů nasazování", + "No Deploy daemons configured" : "Nejsou nastavené žádné procesy služby nasazování", + "Register a custom one or setup from available templates" : "Zaregistrujte uživatelsky určené nebo nastavte z dostupných šablon", "Show details for {appName} app" : "Zobrazit podrobnosti o aplikaci {appName}", "Update to {update}" : "Aktualizovat na {update}", "Remove" : "Odstranit", diff --git a/apps/settings/l10n/da.js b/apps/settings/l10n/da.js index 919547bb98f..b400ed23fce 100644 --- a/apps/settings/l10n/da.js +++ b/apps/settings/l10n/da.js @@ -409,6 +409,10 @@ OC.L10N.register( "Excluded groups" : "Ekskluderede grupper", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Når grupper er valgt/ekskluderet, så anvender de følgende logik til at bestemme, om en konto har 2FA håndhævet: Hvis ingen grupper er valgt, så er 2FA aktiveret for alle, undtagen medlemmer af de ekskluderede grupper. Hvis grupper er valgt, så er 2FA aktiveret for alle medlemmer af disse. Hvis en konto er både i en valgt og ekskluderet gruppe, så har den valgte forrang, og 2FA håndhæves.", "Save changes" : "Gem ændringer", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste over registrerede udgivelses daemoner", + "No Deploy daemons configured" : "Ingen udgivelses daemoner konfigureret", + "Register a custom one or setup from available templates" : "Registrer en brugerdefineret en eller opsæt fra tilgængelige skabeloner", "Show details for {appName} app" : "Vis detaljer for {appName} app", "Update to {update}" : "Opdater til {update}", "Remove" : "Fjern", diff --git a/apps/settings/l10n/da.json b/apps/settings/l10n/da.json index 079a731e357..1449c871cd5 100644 --- a/apps/settings/l10n/da.json +++ b/apps/settings/l10n/da.json @@ -407,6 +407,10 @@ "Excluded groups" : "Ekskluderede grupper", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Når grupper er valgt/ekskluderet, så anvender de følgende logik til at bestemme, om en konto har 2FA håndhævet: Hvis ingen grupper er valgt, så er 2FA aktiveret for alle, undtagen medlemmer af de ekskluderede grupper. Hvis grupper er valgt, så er 2FA aktiveret for alle medlemmer af disse. Hvis en konto er både i en valgt og ekskluderet gruppe, så har den valgte forrang, og 2FA håndhæves.", "Save changes" : "Gem ændringer", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste over registrerede udgivelses daemoner", + "No Deploy daemons configured" : "Ingen udgivelses daemoner konfigureret", + "Register a custom one or setup from available templates" : "Registrer en brugerdefineret en eller opsæt fra tilgængelige skabeloner", "Show details for {appName} app" : "Vis detaljer for {appName} app", "Update to {update}" : "Opdater til {update}", "Remove" : "Fjern", diff --git a/apps/settings/l10n/de.js b/apps/settings/l10n/de.js index 6d41611ef32..9853b70efa7 100644 --- a/apps/settings/l10n/de.js +++ b/apps/settings/l10n/de.js @@ -110,7 +110,7 @@ OC.L10N.register( "Administration" : "Administration", "Users" : "Konten", "Additional settings" : "Zusätzliche Einstellungen", - "Assistant" : "Benötigt keine Übersetzung. Hier wird nur die formelle Übersetzung verwendet (de_DE).", + "Assistant" : "Assistent", "Administration privileges" : "Administrationsrechte", "Groupware" : "Groupware", "Overview" : "Übersicht", @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Ausgeschlossene Gruppen", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Bei der Auswahl/Abwahl von Gruppen wird folgende Logik verwendet, um festzustellen, ob ein Konto 2FA verwenden muss: Wenn keine Gruppe ausgewählt ist, dann wird 2FA für alle Konten aktiviert, außer für solche der ausgenommenen Gruppen. Sind Gruppen ausgewählt, so wird 2FA für alle Kontendieser Gruppen aktiviert. Ist ein Konto sowohl in einer ausgewählten als auch in einer ausgenommenen Gruppe, so hat die Auswahl Vorrang und 2FA wird aktiviert.", "Save changes" : "Änderungen speichern", + "Choose Deploy Daemon for {appName}" : "Bereitstellungs-Daemon für {appName} wählen", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste von registrierten Bereitstellungsdaemons", + "No Deploy daemons configured" : "Keine Bereitstellungsdämonen konfiguriert", + "Register a custom one or setup from available templates" : "Eine benutzerdefinierte Vorlage registrieren oder aus einer verfügbaren Vorlagen einrichten", + "Manage Deploy daemons" : "Bereitstellungs-Daemone verwalten", "Show details for {appName} app" : "Details der App {appName} anzeigen", "Update to {update}" : "Aktualisieren auf {update}", "Remove" : "Entfernen", diff --git a/apps/settings/l10n/de.json b/apps/settings/l10n/de.json index dd4f4b6722a..549b86f45d2 100644 --- a/apps/settings/l10n/de.json +++ b/apps/settings/l10n/de.json @@ -108,7 +108,7 @@ "Administration" : "Administration", "Users" : "Konten", "Additional settings" : "Zusätzliche Einstellungen", - "Assistant" : "Benötigt keine Übersetzung. Hier wird nur die formelle Übersetzung verwendet (de_DE).", + "Assistant" : "Assistent", "Administration privileges" : "Administrationsrechte", "Groupware" : "Groupware", "Overview" : "Übersicht", @@ -418,6 +418,12 @@ "Excluded groups" : "Ausgeschlossene Gruppen", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Bei der Auswahl/Abwahl von Gruppen wird folgende Logik verwendet, um festzustellen, ob ein Konto 2FA verwenden muss: Wenn keine Gruppe ausgewählt ist, dann wird 2FA für alle Konten aktiviert, außer für solche der ausgenommenen Gruppen. Sind Gruppen ausgewählt, so wird 2FA für alle Kontendieser Gruppen aktiviert. Ist ein Konto sowohl in einer ausgewählten als auch in einer ausgenommenen Gruppe, so hat die Auswahl Vorrang und 2FA wird aktiviert.", "Save changes" : "Änderungen speichern", + "Choose Deploy Daemon for {appName}" : "Bereitstellungs-Daemon für {appName} wählen", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste von registrierten Bereitstellungsdaemons", + "No Deploy daemons configured" : "Keine Bereitstellungsdämonen konfiguriert", + "Register a custom one or setup from available templates" : "Eine benutzerdefinierte Vorlage registrieren oder aus einer verfügbaren Vorlagen einrichten", + "Manage Deploy daemons" : "Bereitstellungs-Daemone verwalten", "Show details for {appName} app" : "Details der App {appName} anzeigen", "Update to {update}" : "Aktualisieren auf {update}", "Remove" : "Entfernen", diff --git a/apps/settings/l10n/de_DE.js b/apps/settings/l10n/de_DE.js index 803ebfbce50..4b20c111333 100644 --- a/apps/settings/l10n/de_DE.js +++ b/apps/settings/l10n/de_DE.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Ausgeschlossene Gruppen", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Bei der Auswahl/Abwahl von Gruppen wird folgende Logik verwendet, um festzustellen, ob ein Konto 2FA verwenden muss: Wenn keine Gruppe ausgewählt ist, dann wird 2FA für alle Konten aktiviert, außer für Konten der ausgenommenen Gruppen. Sind Gruppen ausgewählt, so wird 2FA für alle Konten dieser Gruppen aktiviert. Ist ein Konto sowohl in einer ausgewählten als auch in einer ausgenommenen Gruppe, so hat die Auswahl Vorrang und 2FA wird aktiviert.", "Save changes" : "Änderungen speichern ", + "Choose Deploy Daemon for {appName}" : "Bereitstellungs-Daemon für {appName} wählen", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste von registrierten Bereitstellungsdaemons", + "No Deploy daemons configured" : "Keine Bereitstellungs-Daemons konfiguriert", + "Register a custom one or setup from available templates" : "Eine benutzerdefinierte Vorlage registrieren oder aus einer der verfügbaren Vorlagen einrichten", + "Manage Deploy daemons" : "Bereitstellungs-Daemone verwalten", "Show details for {appName} app" : "Details für die App {appName} anzeigen", "Update to {update}" : "Aktualisieren auf {update}", "Remove" : "Entfernen", diff --git a/apps/settings/l10n/de_DE.json b/apps/settings/l10n/de_DE.json index 79a06321a54..8b67f6c8169 100644 --- a/apps/settings/l10n/de_DE.json +++ b/apps/settings/l10n/de_DE.json @@ -418,6 +418,12 @@ "Excluded groups" : "Ausgeschlossene Gruppen", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Bei der Auswahl/Abwahl von Gruppen wird folgende Logik verwendet, um festzustellen, ob ein Konto 2FA verwenden muss: Wenn keine Gruppe ausgewählt ist, dann wird 2FA für alle Konten aktiviert, außer für Konten der ausgenommenen Gruppen. Sind Gruppen ausgewählt, so wird 2FA für alle Konten dieser Gruppen aktiviert. Ist ein Konto sowohl in einer ausgewählten als auch in einer ausgenommenen Gruppe, so hat die Auswahl Vorrang und 2FA wird aktiviert.", "Save changes" : "Änderungen speichern ", + "Choose Deploy Daemon for {appName}" : "Bereitstellungs-Daemon für {appName} wählen", + "Default" : "Standard", + "Registered Deploy daemons list" : "Liste von registrierten Bereitstellungsdaemons", + "No Deploy daemons configured" : "Keine Bereitstellungs-Daemons konfiguriert", + "Register a custom one or setup from available templates" : "Eine benutzerdefinierte Vorlage registrieren oder aus einer der verfügbaren Vorlagen einrichten", + "Manage Deploy daemons" : "Bereitstellungs-Daemone verwalten", "Show details for {appName} app" : "Details für die App {appName} anzeigen", "Update to {update}" : "Aktualisieren auf {update}", "Remove" : "Entfernen", diff --git a/apps/settings/l10n/el.js b/apps/settings/l10n/el.js index 22d66a2a65e..769c6ff263f 100644 --- a/apps/settings/l10n/el.js +++ b/apps/settings/l10n/el.js @@ -406,6 +406,7 @@ OC.L10N.register( "Excluded groups" : "Εξαιρούμενες ομάδες", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Όταν επιλέγονται/εξαιρούνται ομάδες, χρησιμοποιείται η ακόλουθη λογική για να καθοριστεί εάν ένας λογαριασμός έχει επιβληθεί 2FA: Εάν δεν επιλεγούν ομάδες, η 2FA είναι ενεργοποιημένη για όλους εκτός από τα μέλη των εξαιρούμενων ομάδων. Εάν επιλεγούν ομάδες, η 2FA είναι ενεργοποιημένη για όλα τα μέλη αυτών. Εάν ένας λογαριασμός ανήκει ταυτόχρονα σε μια επιλεγμένη και μια εξαιρούμενη ομάδα, η επιλεγμένη ομάδα έχει προτεραιότητα και η 2FA επιβάλλεται.", "Save changes" : "Αποθήκευση αλλαγών", + "Default" : "Προεπιλογή", "Show details for {appName} app" : "Εμφάνιση λεπτομερειών για την εφαρμογή {appName}", "Update to {update}" : "Ενημέρωση σε {update}", "Remove" : "Αφαίρεση", diff --git a/apps/settings/l10n/el.json b/apps/settings/l10n/el.json index 37cf97e43c7..2acff44aec5 100644 --- a/apps/settings/l10n/el.json +++ b/apps/settings/l10n/el.json @@ -404,6 +404,7 @@ "Excluded groups" : "Εξαιρούμενες ομάδες", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Όταν επιλέγονται/εξαιρούνται ομάδες, χρησιμοποιείται η ακόλουθη λογική για να καθοριστεί εάν ένας λογαριασμός έχει επιβληθεί 2FA: Εάν δεν επιλεγούν ομάδες, η 2FA είναι ενεργοποιημένη για όλους εκτός από τα μέλη των εξαιρούμενων ομάδων. Εάν επιλεγούν ομάδες, η 2FA είναι ενεργοποιημένη για όλα τα μέλη αυτών. Εάν ένας λογαριασμός ανήκει ταυτόχρονα σε μια επιλεγμένη και μια εξαιρούμενη ομάδα, η επιλεγμένη ομάδα έχει προτεραιότητα και η 2FA επιβάλλεται.", "Save changes" : "Αποθήκευση αλλαγών", + "Default" : "Προεπιλογή", "Show details for {appName} app" : "Εμφάνιση λεπτομερειών για την εφαρμογή {appName}", "Update to {update}" : "Ενημέρωση σε {update}", "Remove" : "Αφαίρεση", diff --git a/apps/settings/l10n/en_GB.js b/apps/settings/l10n/en_GB.js index 3ee78f9022a..72ce4dd2a2d 100644 --- a/apps/settings/l10n/en_GB.js +++ b/apps/settings/l10n/en_GB.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Excluded groups", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced.", "Save changes" : "Save changes", + "Choose Deploy Daemon for {appName}" : "Choose Deploy Daemon for {appName}", + "Default" : "Default", + "Registered Deploy daemons list" : "Registered Deploy daemons list", + "No Deploy daemons configured" : "No Deploy daemons configured", + "Register a custom one or setup from available templates" : "Register a custom one or setup from available templates", + "Manage Deploy daemons" : "Manage Deploy daemons", "Show details for {appName} app" : "Show details for {appName} app", "Update to {update}" : "Update to {update}", "Remove" : "Remove", diff --git a/apps/settings/l10n/en_GB.json b/apps/settings/l10n/en_GB.json index a1ec7cb0def..7032d0834cf 100644 --- a/apps/settings/l10n/en_GB.json +++ b/apps/settings/l10n/en_GB.json @@ -418,6 +418,12 @@ "Excluded groups" : "Excluded groups", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced.", "Save changes" : "Save changes", + "Choose Deploy Daemon for {appName}" : "Choose Deploy Daemon for {appName}", + "Default" : "Default", + "Registered Deploy daemons list" : "Registered Deploy daemons list", + "No Deploy daemons configured" : "No Deploy daemons configured", + "Register a custom one or setup from available templates" : "Register a custom one or setup from available templates", + "Manage Deploy daemons" : "Manage Deploy daemons", "Show details for {appName} app" : "Show details for {appName} app", "Update to {update}" : "Update to {update}", "Remove" : "Remove", diff --git a/apps/settings/l10n/es.js b/apps/settings/l10n/es.js index c16b29c6ff2..5e8c5938a8c 100644 --- a/apps/settings/l10n/es.js +++ b/apps/settings/l10n/es.js @@ -420,6 +420,10 @@ OC.L10N.register( "Excluded groups" : "Grupos excluidos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Cuando los grupos se seleccionan/excluyen, usan la siguiente lógica para determinar si ua cuenta tiene obligada la 2FA: si no hay grupos seleccionados, 2FA está activa para todos excepto los miembros de los grupos excluidos. Si hay grupos seleccionados, 2FA está activa para todos los miembros de estos. Si una cuenta está a la vez en un grupo seleccionado y otro excluido, el seleccionado tiene preferencia y se obliga la 2FA.", "Save changes" : "Guardar cambios", + "Default" : "Predeterminado", + "Registered Deploy daemons list" : "Lista de daemons de despliegue registrados", + "No Deploy daemons configured" : "No hay daemons de despliegue configurados", + "Register a custom one or setup from available templates" : "Registre uno personalizado, o, configure desde las plantillas disponibles", "Show details for {appName} app" : "Mostrar detalles para la app {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Eliminar", diff --git a/apps/settings/l10n/es.json b/apps/settings/l10n/es.json index d21a84fc0ad..d79ca75b6d6 100644 --- a/apps/settings/l10n/es.json +++ b/apps/settings/l10n/es.json @@ -418,6 +418,10 @@ "Excluded groups" : "Grupos excluidos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Cuando los grupos se seleccionan/excluyen, usan la siguiente lógica para determinar si ua cuenta tiene obligada la 2FA: si no hay grupos seleccionados, 2FA está activa para todos excepto los miembros de los grupos excluidos. Si hay grupos seleccionados, 2FA está activa para todos los miembros de estos. Si una cuenta está a la vez en un grupo seleccionado y otro excluido, el seleccionado tiene preferencia y se obliga la 2FA.", "Save changes" : "Guardar cambios", + "Default" : "Predeterminado", + "Registered Deploy daemons list" : "Lista de daemons de despliegue registrados", + "No Deploy daemons configured" : "No hay daemons de despliegue configurados", + "Register a custom one or setup from available templates" : "Registre uno personalizado, o, configure desde las plantillas disponibles", "Show details for {appName} app" : "Mostrar detalles para la app {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Eliminar", diff --git a/apps/settings/l10n/es_MX.js b/apps/settings/l10n/es_MX.js index 27adccfaaec..675b42c73d7 100644 --- a/apps/settings/l10n/es_MX.js +++ b/apps/settings/l10n/es_MX.js @@ -328,6 +328,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "La autentificación de dos factores no se impone a los miembros de los siguientes grupos.", "Excluded groups" : "Grupos excluidos", "Save changes" : "Guardar cambios", + "Default" : "Por omisión", "Show details for {appName} app" : "Mostrar detalles para la aplicación {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Eliminar", diff --git a/apps/settings/l10n/es_MX.json b/apps/settings/l10n/es_MX.json index 28182b40fec..88bc4e5923a 100644 --- a/apps/settings/l10n/es_MX.json +++ b/apps/settings/l10n/es_MX.json @@ -326,6 +326,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "La autentificación de dos factores no se impone a los miembros de los siguientes grupos.", "Excluded groups" : "Grupos excluidos", "Save changes" : "Guardar cambios", + "Default" : "Por omisión", "Show details for {appName} app" : "Mostrar detalles para la aplicación {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Eliminar", diff --git a/apps/settings/l10n/et_EE.js b/apps/settings/l10n/et_EE.js index 8cd4c980e28..0b088f85c9b 100644 --- a/apps/settings/l10n/et_EE.js +++ b/apps/settings/l10n/et_EE.js @@ -279,6 +279,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Kaheastmeline autentimine pole kohustuslik nende gruppide liikmetele.", "Excluded groups" : "Välistatud neis gruppides", "Save changes" : "Salvesta muudatused", + "Default" : "Vaikimisi", "Show details for {appName} app" : "Näita „{appName}“ rakenduse üksikasju", "Update to {update}" : "Uuenda versioonini {update}", "Remove" : "Eemalda", diff --git a/apps/settings/l10n/et_EE.json b/apps/settings/l10n/et_EE.json index f2b27aa070b..f4bba17b90b 100644 --- a/apps/settings/l10n/et_EE.json +++ b/apps/settings/l10n/et_EE.json @@ -277,6 +277,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Kaheastmeline autentimine pole kohustuslik nende gruppide liikmetele.", "Excluded groups" : "Välistatud neis gruppides", "Save changes" : "Salvesta muudatused", + "Default" : "Vaikimisi", "Show details for {appName} app" : "Näita „{appName}“ rakenduse üksikasju", "Update to {update}" : "Uuenda versioonini {update}", "Remove" : "Eemalda", diff --git a/apps/settings/l10n/eu.js b/apps/settings/l10n/eu.js index 7114ff32e12..6d73738fbe2 100644 --- a/apps/settings/l10n/eu.js +++ b/apps/settings/l10n/eu.js @@ -396,6 +396,7 @@ OC.L10N.register( "Excluded groups" : "Baztertu taldeak", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Taldeak aukeratuta daudenean hauxe erabiltzen da kontuentzat bi faktoreko autentikazioa derrigortu ala ez erabakitzeko: talderik ez badago aukeratuta, guztientzat dago derrigortuta kanpoan utzitako taldeetako kideentzat ezik. Taldeak aukeratuta badaude, beren kideentzat derrigortzen da. Kontu bat aukeratutako eta kanpoan utzitako talde bateko kide bada, aukeratutako taldekoa izatea lehenesten da eta bi faktoreko autentikazioa erabiltzera derrigortzen da.", "Save changes" : "Gorde aldaketak", + "Default" : "Lehenetsia", "Show details for {appName} app" : "Erakutsi {appName} aplikazioaren xehetasunak", "Update to {update}" : "Eguneratu {update} bertsiora", "Remove" : "Ezabatu", diff --git a/apps/settings/l10n/eu.json b/apps/settings/l10n/eu.json index d24682f5c48..61b434e81af 100644 --- a/apps/settings/l10n/eu.json +++ b/apps/settings/l10n/eu.json @@ -394,6 +394,7 @@ "Excluded groups" : "Baztertu taldeak", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Taldeak aukeratuta daudenean hauxe erabiltzen da kontuentzat bi faktoreko autentikazioa derrigortu ala ez erabakitzeko: talderik ez badago aukeratuta, guztientzat dago derrigortuta kanpoan utzitako taldeetako kideentzat ezik. Taldeak aukeratuta badaude, beren kideentzat derrigortzen da. Kontu bat aukeratutako eta kanpoan utzitako talde bateko kide bada, aukeratutako taldekoa izatea lehenesten da eta bi faktoreko autentikazioa erabiltzera derrigortzen da.", "Save changes" : "Gorde aldaketak", + "Default" : "Lehenetsia", "Show details for {appName} app" : "Erakutsi {appName} aplikazioaren xehetasunak", "Update to {update}" : "Eguneratu {update} bertsiora", "Remove" : "Ezabatu", diff --git a/apps/settings/l10n/fa.js b/apps/settings/l10n/fa.js index 722b9b0f35d..cdd90bb2049 100644 --- a/apps/settings/l10n/fa.js +++ b/apps/settings/l10n/fa.js @@ -167,6 +167,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Two-factor authentication is not enforced for members of the following groups.", "Excluded groups" : "گروههای مستثنی", "Save changes" : "ذخیرهٔ تغییرات", + "Default" : "پیشفرض", "Update to {update}" : "بهروز رسانی به {update} ", "Remove" : "برداشتن", "Featured" : "برگزیده", diff --git a/apps/settings/l10n/fa.json b/apps/settings/l10n/fa.json index 024682d4f2f..3c0fad6f445 100644 --- a/apps/settings/l10n/fa.json +++ b/apps/settings/l10n/fa.json @@ -165,6 +165,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Two-factor authentication is not enforced for members of the following groups.", "Excluded groups" : "گروههای مستثنی", "Save changes" : "ذخیرهٔ تغییرات", + "Default" : "پیشفرض", "Update to {update}" : "بهروز رسانی به {update} ", "Remove" : "برداشتن", "Featured" : "برگزیده", diff --git a/apps/settings/l10n/fi.js b/apps/settings/l10n/fi.js index be4c3236db4..f8f5cece53f 100644 --- a/apps/settings/l10n/fi.js +++ b/apps/settings/l10n/fi.js @@ -192,6 +192,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Kaksivaiheisen tunnistautuminen ei ole pakotettu seuraavien ryhmien jäsenille.", "Excluded groups" : "Poissuljetut ryhmät", "Save changes" : "Tallenna muutokset", + "Default" : "Oletus", "Show details for {appName} app" : "Näytä sovelluksen {appName} tiedot", "Update to {update}" : "Päivitä versioon {update}", "Remove" : "Poista", diff --git a/apps/settings/l10n/fi.json b/apps/settings/l10n/fi.json index db35823df9f..7f9df2b5ac6 100644 --- a/apps/settings/l10n/fi.json +++ b/apps/settings/l10n/fi.json @@ -190,6 +190,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Kaksivaiheisen tunnistautuminen ei ole pakotettu seuraavien ryhmien jäsenille.", "Excluded groups" : "Poissuljetut ryhmät", "Save changes" : "Tallenna muutokset", + "Default" : "Oletus", "Show details for {appName} app" : "Näytä sovelluksen {appName} tiedot", "Update to {update}" : "Päivitä versioon {update}", "Remove" : "Poista", diff --git a/apps/settings/l10n/fr.js b/apps/settings/l10n/fr.js index 2babdeb552e..240fa729101 100644 --- a/apps/settings/l10n/fr.js +++ b/apps/settings/l10n/fr.js @@ -417,6 +417,10 @@ OC.L10N.register( "Excluded groups" : "Groupes exclus", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (A2F) est imposée à un compte. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un compte est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.", "Save changes" : "Enregistrer les modifications", + "Default" : "Défaut", + "Registered Deploy daemons list" : "Liste des services de déploiement enregistrés", + "No Deploy daemons configured" : "Aucun service de déploiement configuré", + "Register a custom one or setup from available templates" : "Enregistrez-en un personnalisé ou une configuration à partir des modèles disponibles", "Show details for {appName} app" : "Afficher les détails de l'application {appName}", "Update to {update}" : "Mettre à jour vers {update}", "Remove" : "Retirer", diff --git a/apps/settings/l10n/fr.json b/apps/settings/l10n/fr.json index 1d62d0e38f7..9c92ac74301 100644 --- a/apps/settings/l10n/fr.json +++ b/apps/settings/l10n/fr.json @@ -415,6 +415,10 @@ "Excluded groups" : "Groupes exclus", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (A2F) est imposée à un compte. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un compte est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.", "Save changes" : "Enregistrer les modifications", + "Default" : "Défaut", + "Registered Deploy daemons list" : "Liste des services de déploiement enregistrés", + "No Deploy daemons configured" : "Aucun service de déploiement configuré", + "Register a custom one or setup from available templates" : "Enregistrez-en un personnalisé ou une configuration à partir des modèles disponibles", "Show details for {appName} app" : "Afficher les détails de l'application {appName}", "Update to {update}" : "Mettre à jour vers {update}", "Remove" : "Retirer", diff --git a/apps/settings/l10n/ga.js b/apps/settings/l10n/ga.js index aca46027a3a..c366c9d54d5 100644 --- a/apps/settings/l10n/ga.js +++ b/apps/settings/l10n/ga.js @@ -420,6 +420,10 @@ OC.L10N.register( "Excluded groups" : "Grúpaí eisiata", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Nuair a roghnaítear/eisiatar grúpaí, úsáideann siad an loighic seo a leanas le fáil amach an bhfuil 2FA curtha i bhfeidhm ag cuntas: Mura roghnaítear aon ghrúpa, tá 2FA cumasaithe do gach duine seachas baill de na grúpaí eisiata. Má roghnaítear grúpaí, tá 2FA cumasaithe do gach ball díobh seo. Má tá cuntas i ngrúpa roghnaithe agus eisiata araon, beidh tosaíocht ag an gceann roghnaithe agus cuirtear 2FA i bhfeidhm.", "Save changes" : "Sabháil na hathruithe", + "Default" : "Réamhshocrú", + "Registered Deploy daemons list" : "Liosta deamhan Imscaradh Cláraithe", + "No Deploy daemons configured" : "Níl aon deamhan Imscaradh cumraithe", + "Register a custom one or setup from available templates" : "Cláraigh ceann nó socrú saincheaptha ó na teimpléid atá ar fáil", "Show details for {appName} app" : "Taispeáin sonraí na haipe {appName}", "Update to {update}" : "Nuashonraigh go {update}", "Remove" : "Bain", diff --git a/apps/settings/l10n/ga.json b/apps/settings/l10n/ga.json index dd408c70d47..46aa6439a2f 100644 --- a/apps/settings/l10n/ga.json +++ b/apps/settings/l10n/ga.json @@ -418,6 +418,10 @@ "Excluded groups" : "Grúpaí eisiata", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Nuair a roghnaítear/eisiatar grúpaí, úsáideann siad an loighic seo a leanas le fáil amach an bhfuil 2FA curtha i bhfeidhm ag cuntas: Mura roghnaítear aon ghrúpa, tá 2FA cumasaithe do gach duine seachas baill de na grúpaí eisiata. Má roghnaítear grúpaí, tá 2FA cumasaithe do gach ball díobh seo. Má tá cuntas i ngrúpa roghnaithe agus eisiata araon, beidh tosaíocht ag an gceann roghnaithe agus cuirtear 2FA i bhfeidhm.", "Save changes" : "Sabháil na hathruithe", + "Default" : "Réamhshocrú", + "Registered Deploy daemons list" : "Liosta deamhan Imscaradh Cláraithe", + "No Deploy daemons configured" : "Níl aon deamhan Imscaradh cumraithe", + "Register a custom one or setup from available templates" : "Cláraigh ceann nó socrú saincheaptha ó na teimpléid atá ar fáil", "Show details for {appName} app" : "Taispeáin sonraí na haipe {appName}", "Update to {update}" : "Nuashonraigh go {update}", "Remove" : "Bain", diff --git a/apps/settings/l10n/gl.js b/apps/settings/l10n/gl.js index 98c76a26a18..9e6097091c9 100644 --- a/apps/settings/l10n/gl.js +++ b/apps/settings/l10n/gl.js @@ -407,6 +407,10 @@ OC.L10N.register( "Excluded groups" : "Grupos excluídos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Cando se seleccionan/exclúen os grupos, usase a seguinte lóxica para determinar se unha conta ten obrigado o 2FA: Se non hai grupos seleccionados, o 2FA está activo para todos agás os membros dos grupos excluídos. Se hai grupos seleccionados, o 2FA está activo para todos os membros destes. Se unha conta está á vez nun grupo seleccionado e noutro excluído, o seleccionado ten preferencia e se lle obriga a 2FA.", "Save changes" : "Gardar os cambios", + "Default" : "Predeterminado", + "Registered Deploy daemons list" : "Lista de servizos de despregadura rexistrados", + "No Deploy daemons configured" : "Non hai ningún servizo de despregadura configurado", + "Register a custom one or setup from available templates" : "Rexistre un personalizado ou configúreo a partir dos modelos dispoñíbeis", "Show details for {appName} app" : "Amosar os detalles da aplicación {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Retirar", diff --git a/apps/settings/l10n/gl.json b/apps/settings/l10n/gl.json index 542654fbff8..f7208480349 100644 --- a/apps/settings/l10n/gl.json +++ b/apps/settings/l10n/gl.json @@ -405,6 +405,10 @@ "Excluded groups" : "Grupos excluídos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Cando se seleccionan/exclúen os grupos, usase a seguinte lóxica para determinar se unha conta ten obrigado o 2FA: Se non hai grupos seleccionados, o 2FA está activo para todos agás os membros dos grupos excluídos. Se hai grupos seleccionados, o 2FA está activo para todos os membros destes. Se unha conta está á vez nun grupo seleccionado e noutro excluído, o seleccionado ten preferencia e se lle obriga a 2FA.", "Save changes" : "Gardar os cambios", + "Default" : "Predeterminado", + "Registered Deploy daemons list" : "Lista de servizos de despregadura rexistrados", + "No Deploy daemons configured" : "Non hai ningún servizo de despregadura configurado", + "Register a custom one or setup from available templates" : "Rexistre un personalizado ou configúreo a partir dos modelos dispoñíbeis", "Show details for {appName} app" : "Amosar os detalles da aplicación {appName}", "Update to {update}" : "Actualizar a {update}", "Remove" : "Retirar", diff --git a/apps/settings/l10n/he.js b/apps/settings/l10n/he.js index c3ce45f5edf..a9140cb6db6 100644 --- a/apps/settings/l10n/he.js +++ b/apps/settings/l10n/he.js @@ -135,6 +135,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "אימות דו־שלבי לא נאכף על החברים בקבוצות הבאות.", "Excluded groups" : "קבוצות חריגות", "Save changes" : "שמירת שינויים", + "Default" : "ברירת מחדל", "Update to {update}" : "עדכון ל־{update}", "Remove" : "הסרה", "Featured" : "מומלץ", diff --git a/apps/settings/l10n/he.json b/apps/settings/l10n/he.json index ba34f81b61f..e90cf60f89b 100644 --- a/apps/settings/l10n/he.json +++ b/apps/settings/l10n/he.json @@ -133,6 +133,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "אימות דו־שלבי לא נאכף על החברים בקבוצות הבאות.", "Excluded groups" : "קבוצות חריגות", "Save changes" : "שמירת שינויים", + "Default" : "ברירת מחדל", "Update to {update}" : "עדכון ל־{update}", "Remove" : "הסרה", "Featured" : "מומלץ", diff --git a/apps/settings/l10n/hr.js b/apps/settings/l10n/hr.js index 1fbf446ef49..86cdad181df 100644 --- a/apps/settings/l10n/hr.js +++ b/apps/settings/l10n/hr.js @@ -146,6 +146,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Dvofaktorska autentifikacija ne primjenjuje se na članove sljedećih grupa.", "Excluded groups" : "Izuzete grupe", "Save changes" : "Spremi promjene", + "Default" : "Zadani", "Update to {update}" : "Ažuriraj na {update}", "Remove" : "Ukloni", "Featured" : "Istaknuto", diff --git a/apps/settings/l10n/hr.json b/apps/settings/l10n/hr.json index a951e332caa..bc35715f96f 100644 --- a/apps/settings/l10n/hr.json +++ b/apps/settings/l10n/hr.json @@ -144,6 +144,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Dvofaktorska autentifikacija ne primjenjuje se na članove sljedećih grupa.", "Excluded groups" : "Izuzete grupe", "Save changes" : "Spremi promjene", + "Default" : "Zadani", "Update to {update}" : "Ažuriraj na {update}", "Remove" : "Ukloni", "Featured" : "Istaknuto", diff --git a/apps/settings/l10n/hu.js b/apps/settings/l10n/hu.js index d9647870e62..cecf1096af4 100644 --- a/apps/settings/l10n/hu.js +++ b/apps/settings/l10n/hu.js @@ -278,6 +278,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "A kétfaktoros hitelesítés az alábbi csoportok számára nem kötelező.", "Excluded groups" : "Kizárt csoportok", "Save changes" : "Változások mentése", + "Default" : "Alapértelmezett", "Update to {update}" : "Frissítés erre: {update}", "Remove" : "Eltávolítás", "Featured" : "Kiemelt", diff --git a/apps/settings/l10n/hu.json b/apps/settings/l10n/hu.json index 47f79605d75..836e8b98a8c 100644 --- a/apps/settings/l10n/hu.json +++ b/apps/settings/l10n/hu.json @@ -276,6 +276,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "A kétfaktoros hitelesítés az alábbi csoportok számára nem kötelező.", "Excluded groups" : "Kizárt csoportok", "Save changes" : "Változások mentése", + "Default" : "Alapértelmezett", "Update to {update}" : "Frissítés erre: {update}", "Remove" : "Eltávolítás", "Featured" : "Kiemelt", diff --git a/apps/settings/l10n/id.js b/apps/settings/l10n/id.js index fc56ade4e18..0e97d4b9403 100644 --- a/apps/settings/l10n/id.js +++ b/apps/settings/l10n/id.js @@ -135,6 +135,7 @@ OC.L10N.register( "Limit to groups" : "Batasi ke grup", "Excluded groups" : "Grup yang dikecualikan", "Save changes" : "Simpan perubahan", + "Default" : "Default", "Update to {update}" : "Perbarui ke {update}", "Remove" : "Hapus", "Featured" : "Unggulan", diff --git a/apps/settings/l10n/id.json b/apps/settings/l10n/id.json index bba6302b4f0..598e786e5e8 100644 --- a/apps/settings/l10n/id.json +++ b/apps/settings/l10n/id.json @@ -133,6 +133,7 @@ "Limit to groups" : "Batasi ke grup", "Excluded groups" : "Grup yang dikecualikan", "Save changes" : "Simpan perubahan", + "Default" : "Default", "Update to {update}" : "Perbarui ke {update}", "Remove" : "Hapus", "Featured" : "Unggulan", diff --git a/apps/settings/l10n/is.js b/apps/settings/l10n/is.js index c3acf78c502..114f7df710c 100644 --- a/apps/settings/l10n/is.js +++ b/apps/settings/l10n/is.js @@ -264,6 +264,7 @@ OC.L10N.register( "Excluded groups" : "Útilokaðir hópar", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Þegar hópar eru valdir/útilokaðir, styðjast þeir við eftirfarandi röksemdafærslu til að ákvarða hvort notandaaðgangur sé með þvingaða/virka tveggja-þrepa auðkenningu: tveggja-þrepa auðkenning er virk fyrir alla nema meðlimi útilokaðra hópa. Ef hópar eru valdir, er tveggja-þrepa auðkenning virk fyrir alla meðlimi þessara hópa. Ef notandi er bæði í völdum og í útilokuðum hópum, ræður valið og því er tveggja-þrepa auðkenning virk.", "Save changes" : "Vista breytingar", + "Default" : "Sjálfgefið", "Show details for {appName} app" : "Birta ítarlegri upplýsingar fyrir {appName} forritið", "Update to {update}" : "Uppfæra í {update}", "Remove" : "Fjarlægja", diff --git a/apps/settings/l10n/is.json b/apps/settings/l10n/is.json index 7d14270a460..ebdde060c2d 100644 --- a/apps/settings/l10n/is.json +++ b/apps/settings/l10n/is.json @@ -262,6 +262,7 @@ "Excluded groups" : "Útilokaðir hópar", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Þegar hópar eru valdir/útilokaðir, styðjast þeir við eftirfarandi röksemdafærslu til að ákvarða hvort notandaaðgangur sé með þvingaða/virka tveggja-þrepa auðkenningu: tveggja-þrepa auðkenning er virk fyrir alla nema meðlimi útilokaðra hópa. Ef hópar eru valdir, er tveggja-þrepa auðkenning virk fyrir alla meðlimi þessara hópa. Ef notandi er bæði í völdum og í útilokuðum hópum, ræður valið og því er tveggja-þrepa auðkenning virk.", "Save changes" : "Vista breytingar", + "Default" : "Sjálfgefið", "Show details for {appName} app" : "Birta ítarlegri upplýsingar fyrir {appName} forritið", "Update to {update}" : "Uppfæra í {update}", "Remove" : "Fjarlægja", diff --git a/apps/settings/l10n/it.js b/apps/settings/l10n/it.js index 0353bb2378b..ad2832679e7 100644 --- a/apps/settings/l10n/it.js +++ b/apps/settings/l10n/it.js @@ -342,6 +342,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "L'autenticazione a due fattori non è applicata per i membri dei gruppi seguenti.", "Excluded groups" : "Gruppi esclusi", "Save changes" : "Salva le modifiche", + "Default" : "Predefinito", "Show details for {appName} app" : "Mostra dettagli per l'applicazione {appName}", "Update to {update}" : "Aggiorna a {update}", "Remove" : "Rimuovi", diff --git a/apps/settings/l10n/it.json b/apps/settings/l10n/it.json index f88a69d37c4..0948a265946 100644 --- a/apps/settings/l10n/it.json +++ b/apps/settings/l10n/it.json @@ -340,6 +340,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "L'autenticazione a due fattori non è applicata per i membri dei gruppi seguenti.", "Excluded groups" : "Gruppi esclusi", "Save changes" : "Salva le modifiche", + "Default" : "Predefinito", "Show details for {appName} app" : "Mostra dettagli per l'applicazione {appName}", "Update to {update}" : "Aggiorna a {update}", "Remove" : "Rimuovi", diff --git a/apps/settings/l10n/ja.js b/apps/settings/l10n/ja.js index 8920b1b597d..ccbadd44f71 100644 --- a/apps/settings/l10n/ja.js +++ b/apps/settings/l10n/ja.js @@ -419,6 +419,10 @@ OC.L10N.register( "Excluded groups" : "除外するグループ", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "グループが選択または除外されている場合、アカウントに対して二要素認証を強制するかを次のロジックで判断します: 強制するグループが未選択の場合、除外するグループのメンバーを除く全アカウントに対して二要素認証を強制します。強制するグループが選択されている場合、強制するグループの全メンバーに対して二要素認証を強制します。両方のグループに所属するアカウントに対しては、強制するグループが優先され、二要素認証が強制されます。", "Save changes" : "変更を保存", + "Default" : "デフォルト", + "Registered Deploy daemons list" : "登録済みデプロイデーモンリスト", + "No Deploy daemons configured" : "デプロイデーモンが設定されていません", + "Register a custom one or setup from available templates" : "カスタムのものを登録するか利用可能なテンプレートから設定する", "Show details for {appName} app" : "{appName} アプリの詳細を表示する", "Update to {update}" : "{update} にアップデート", "Remove" : "削除", diff --git a/apps/settings/l10n/ja.json b/apps/settings/l10n/ja.json index 2520b747166..09a45f8b22e 100644 --- a/apps/settings/l10n/ja.json +++ b/apps/settings/l10n/ja.json @@ -417,6 +417,10 @@ "Excluded groups" : "除外するグループ", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "グループが選択または除外されている場合、アカウントに対して二要素認証を強制するかを次のロジックで判断します: 強制するグループが未選択の場合、除外するグループのメンバーを除く全アカウントに対して二要素認証を強制します。強制するグループが選択されている場合、強制するグループの全メンバーに対して二要素認証を強制します。両方のグループに所属するアカウントに対しては、強制するグループが優先され、二要素認証が強制されます。", "Save changes" : "変更を保存", + "Default" : "デフォルト", + "Registered Deploy daemons list" : "登録済みデプロイデーモンリスト", + "No Deploy daemons configured" : "デプロイデーモンが設定されていません", + "Register a custom one or setup from available templates" : "カスタムのものを登録するか利用可能なテンプレートから設定する", "Show details for {appName} app" : "{appName} アプリの詳細を表示する", "Update to {update}" : "{update} にアップデート", "Remove" : "削除", diff --git a/apps/settings/l10n/ka.js b/apps/settings/l10n/ka.js index 877c8393c9a..c75c818f8d5 100644 --- a/apps/settings/l10n/ka.js +++ b/apps/settings/l10n/ka.js @@ -238,6 +238,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Two-factor authentication is not enforced for members of the following groups.", "Excluded groups" : "Excluded groups", "Save changes" : "Save changes", + "Default" : "Default", "Show details for {appName} app" : "Show details for {appName} app", "Update to {update}" : "Update to {update}", "Remove" : "Remove", diff --git a/apps/settings/l10n/ka.json b/apps/settings/l10n/ka.json index 900f6ab9d2d..cf8b50eb6a7 100644 --- a/apps/settings/l10n/ka.json +++ b/apps/settings/l10n/ka.json @@ -236,6 +236,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Two-factor authentication is not enforced for members of the following groups.", "Excluded groups" : "Excluded groups", "Save changes" : "Save changes", + "Default" : "Default", "Show details for {appName} app" : "Show details for {appName} app", "Update to {update}" : "Update to {update}", "Remove" : "Remove", diff --git a/apps/settings/l10n/ka_GE.js b/apps/settings/l10n/ka_GE.js index a0142c394ca..3e24934bd46 100644 --- a/apps/settings/l10n/ka_GE.js +++ b/apps/settings/l10n/ka_GE.js @@ -96,6 +96,7 @@ OC.L10N.register( "Default share permissions" : "საწყისი გაზიარების პარამეტრები", "Limit to groups" : "ლიმიტი ჯგუფებზე", "Save changes" : "ცვილებების შენახვა", + "Default" : "საწყისი პარამეტრები", "Remove" : "წაშლა", "Featured" : "გამორჩეულები", "Icon" : "პიქტოგრამა", diff --git a/apps/settings/l10n/ka_GE.json b/apps/settings/l10n/ka_GE.json index 3b88d0c8af5..897d4063778 100644 --- a/apps/settings/l10n/ka_GE.json +++ b/apps/settings/l10n/ka_GE.json @@ -94,6 +94,7 @@ "Default share permissions" : "საწყისი გაზიარების პარამეტრები", "Limit to groups" : "ლიმიტი ჯგუფებზე", "Save changes" : "ცვილებების შენახვა", + "Default" : "საწყისი პარამეტრები", "Remove" : "წაშლა", "Featured" : "გამორჩეულები", "Icon" : "პიქტოგრამა", diff --git a/apps/settings/l10n/ko.js b/apps/settings/l10n/ko.js index 56e531531af..ce74a53a433 100644 --- a/apps/settings/l10n/ko.js +++ b/apps/settings/l10n/ko.js @@ -341,6 +341,10 @@ OC.L10N.register( "Excluded groups" : "제외된 그룹", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "그룹을 선택하거나 제외하면 이들의 계정에 2FA를 강제할지 다음 로직을 통해 결정합니다: 아무 그룹도 선택하지 않으면, 제외된 그룹의 사용자들을 제외한 모두에게 2FA가 활성화됩니다. 그룹을 선택했다면, 그 그룹(들)의 모든 멤버들에게 2FA가 활성화됩니다. 하나의 계정이 선택한 그룹과 제외한 그룹에 동시에 있다면, 선택한 그룹이 우선순위를 가져 2FA가 강제됩니다.", "Save changes" : "변경 사항 저장", + "Default" : "디폴트", + "Registered Deploy daemons list" : "등록된 배포 데몬 목록", + "No Deploy daemons configured" : "배포 데몬이 구성되지 않았습니다.", + "Register a custom one or setup from available templates" : "사용자 정의 항목을 등록하거나 사용 가능한 템플릿에서 설정", "Show details for {appName} app" : "{appName} 앱에 대한 상세 정보 보기", "Update to {update}" : "{update}(으)로 업데이트", "Remove" : "삭제", diff --git a/apps/settings/l10n/ko.json b/apps/settings/l10n/ko.json index df148744c83..b45adc7f3c5 100644 --- a/apps/settings/l10n/ko.json +++ b/apps/settings/l10n/ko.json @@ -339,6 +339,10 @@ "Excluded groups" : "제외된 그룹", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "그룹을 선택하거나 제외하면 이들의 계정에 2FA를 강제할지 다음 로직을 통해 결정합니다: 아무 그룹도 선택하지 않으면, 제외된 그룹의 사용자들을 제외한 모두에게 2FA가 활성화됩니다. 그룹을 선택했다면, 그 그룹(들)의 모든 멤버들에게 2FA가 활성화됩니다. 하나의 계정이 선택한 그룹과 제외한 그룹에 동시에 있다면, 선택한 그룹이 우선순위를 가져 2FA가 강제됩니다.", "Save changes" : "변경 사항 저장", + "Default" : "디폴트", + "Registered Deploy daemons list" : "등록된 배포 데몬 목록", + "No Deploy daemons configured" : "배포 데몬이 구성되지 않았습니다.", + "Register a custom one or setup from available templates" : "사용자 정의 항목을 등록하거나 사용 가능한 템플릿에서 설정", "Show details for {appName} app" : "{appName} 앱에 대한 상세 정보 보기", "Update to {update}" : "{update}(으)로 업데이트", "Remove" : "삭제", diff --git a/apps/settings/l10n/lt_LT.js b/apps/settings/l10n/lt_LT.js index 692612a36cd..65f69a9b2f6 100644 --- a/apps/settings/l10n/lt_LT.js +++ b/apps/settings/l10n/lt_LT.js @@ -165,6 +165,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Dviejų faktorių tapatybės nustatymas nėra priverstinis šių grupių nariams.", "Excluded groups" : "Pašalintos grupės", "Save changes" : "Įrašyti pakeitimus", + "Default" : "Numatytasis", "Update to {update}" : "Atnaujinti į {update}", "Remove" : "Šalinti", "Featured" : "Siūlomos", diff --git a/apps/settings/l10n/lt_LT.json b/apps/settings/l10n/lt_LT.json index 60c6f1098a1..e2057a353e5 100644 --- a/apps/settings/l10n/lt_LT.json +++ b/apps/settings/l10n/lt_LT.json @@ -163,6 +163,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Dviejų faktorių tapatybės nustatymas nėra priverstinis šių grupių nariams.", "Excluded groups" : "Pašalintos grupės", "Save changes" : "Įrašyti pakeitimus", + "Default" : "Numatytasis", "Update to {update}" : "Atnaujinti į {update}", "Remove" : "Šalinti", "Featured" : "Siūlomos", diff --git a/apps/settings/l10n/mk.js b/apps/settings/l10n/mk.js index 69f33411fe7..163cbac7f65 100644 --- a/apps/settings/l10n/mk.js +++ b/apps/settings/l10n/mk.js @@ -176,6 +176,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Двофакторна автентикација не е задолжителна за членови на следниве групи.", "Excluded groups" : "Исклучени групи", "Save changes" : "Зачувај ги промените", + "Default" : "Предефиниран", "Update to {update}" : "Надгради на {update}", "Remove" : "Отстрани ", "Featured" : "Истакнати", diff --git a/apps/settings/l10n/mk.json b/apps/settings/l10n/mk.json index 16c9c0b7dd0..ca957571a50 100644 --- a/apps/settings/l10n/mk.json +++ b/apps/settings/l10n/mk.json @@ -174,6 +174,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Двофакторна автентикација не е задолжителна за членови на следниве групи.", "Excluded groups" : "Исклучени групи", "Save changes" : "Зачувај ги промените", + "Default" : "Предефиниран", "Update to {update}" : "Надгради на {update}", "Remove" : "Отстрани ", "Featured" : "Истакнати", diff --git a/apps/settings/l10n/nb.js b/apps/settings/l10n/nb.js index 63a02b3c9de..8f65a357f92 100644 --- a/apps/settings/l10n/nb.js +++ b/apps/settings/l10n/nb.js @@ -383,6 +383,10 @@ OC.L10N.register( "Excluded groups" : "Utelukkede grupper", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Når grupper velges/ekskluderes, bruker de følgende logikk for å avgjøre om en konto har 2FA håndhevet: Hvis ingen grupper er valgt, aktiveres 2FA for alle unntatt medlemmer av de ekskluderte gruppene. Hvis grupper velges, er 2FA aktivert for alle medlemmer av disse. Hvis en konto er både i en valgt og ekskludert gruppe, har den valgte forrang og 2FA håndheves.", "Save changes" : "Lagre endringer", + "Default" : "Forvalg", + "Registered Deploy daemons list" : "Registrert liste over distribuerings-daemoner", + "No Deploy daemons configured" : "Ingen distribuerings-daemoner konfigurert", + "Register a custom one or setup from available templates" : "Registrer en tilpasset en eller sett opp fra tilgjengelige maler", "Show details for {appName} app" : "Vis detaljer for {appName}-app", "Update to {update}" : "Oppdater til {update}", "Remove" : "Fjern", diff --git a/apps/settings/l10n/nb.json b/apps/settings/l10n/nb.json index 4fcd2dc324a..5018b931c3a 100644 --- a/apps/settings/l10n/nb.json +++ b/apps/settings/l10n/nb.json @@ -381,6 +381,10 @@ "Excluded groups" : "Utelukkede grupper", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Når grupper velges/ekskluderes, bruker de følgende logikk for å avgjøre om en konto har 2FA håndhevet: Hvis ingen grupper er valgt, aktiveres 2FA for alle unntatt medlemmer av de ekskluderte gruppene. Hvis grupper velges, er 2FA aktivert for alle medlemmer av disse. Hvis en konto er både i en valgt og ekskludert gruppe, har den valgte forrang og 2FA håndheves.", "Save changes" : "Lagre endringer", + "Default" : "Forvalg", + "Registered Deploy daemons list" : "Registrert liste over distribuerings-daemoner", + "No Deploy daemons configured" : "Ingen distribuerings-daemoner konfigurert", + "Register a custom one or setup from available templates" : "Registrer en tilpasset en eller sett opp fra tilgjengelige maler", "Show details for {appName} app" : "Vis detaljer for {appName}-app", "Update to {update}" : "Oppdater til {update}", "Remove" : "Fjern", diff --git a/apps/settings/l10n/nl.js b/apps/settings/l10n/nl.js index 85c28efee86..b4bac2e9c79 100644 --- a/apps/settings/l10n/nl.js +++ b/apps/settings/l10n/nl.js @@ -262,6 +262,10 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Tweestapsverificatie wordt niet geforceerd voor leden van de volgende groepen.", "Excluded groups" : "Uitgesloten groepen", "Save changes" : "Wijzigingen bewaren", + "Default" : "Standaard", + "Registered Deploy daemons list" : "Lijst van geregistreerde Deploy Daemons", + "No Deploy daemons configured" : "Geen Deploy Daemons geconfigureerd", + "Register a custom one or setup from available templates" : "Registreer met de hand of gebruik een van de beschikbare templates", "Show details for {appName} app" : "Toon details voor {appName} app", "Update to {update}" : "Update naar {update}", "Remove" : "Verwijderen", diff --git a/apps/settings/l10n/nl.json b/apps/settings/l10n/nl.json index 461db20a987..42f5712d167 100644 --- a/apps/settings/l10n/nl.json +++ b/apps/settings/l10n/nl.json @@ -260,6 +260,10 @@ "Two-factor authentication is not enforced for members of the following groups." : "Tweestapsverificatie wordt niet geforceerd voor leden van de volgende groepen.", "Excluded groups" : "Uitgesloten groepen", "Save changes" : "Wijzigingen bewaren", + "Default" : "Standaard", + "Registered Deploy daemons list" : "Lijst van geregistreerde Deploy Daemons", + "No Deploy daemons configured" : "Geen Deploy Daemons geconfigureerd", + "Register a custom one or setup from available templates" : "Registreer met de hand of gebruik een van de beschikbare templates", "Show details for {appName} app" : "Toon details voor {appName} app", "Update to {update}" : "Update naar {update}", "Remove" : "Verwijderen", diff --git a/apps/settings/l10n/pl.js b/apps/settings/l10n/pl.js index 3600a6a3607..fa8e66874c1 100644 --- a/apps/settings/l10n/pl.js +++ b/apps/settings/l10n/pl.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Wyłączone grupy", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Gdy grupy są zaznaczone lub wykluczone, stosowana jest następująca logika w celu określenia, czy dla konta ma być wymuszona 2FA:. Jeśli nie wybrano żadnych grup, 2FA jest włączone dla wszystkich, z wyjątkiem członków wykluczonych grup. Jeśli grupy są wybrane, 2FA jest włączone dla wszystkich ich członków. Jeśli konto należy zarówno do grupy wybranej, jak i wykluczonej, priorytet ma grupa wybrana i 2FA jest wymuszane.", "Save changes" : "Zapisz zmiany", + "Choose Deploy Daemon for {appName}" : "Wybierz demona wdrożeniowego dla {appName}", + "Default" : "Domyślny", + "Registered Deploy daemons list" : "Lista zarejestrowanych demonów wdrożeniowych", + "No Deploy daemons configured" : "Brak skonfigurowanych demonów wdrożeniowych", + "Register a custom one or setup from available templates" : "Zarejestruj własnego lub skonfiguruj z dostępnych szablonów", + "Manage Deploy daemons" : "Zarządzaj demonami wdrożeniowymi", "Show details for {appName} app" : "Pokaż szczegóły aplikacji {appName}", "Update to {update}" : "Zaktualizuj do {update}", "Remove" : "Usuń", diff --git a/apps/settings/l10n/pl.json b/apps/settings/l10n/pl.json index 882596caf2f..db6334e6d5d 100644 --- a/apps/settings/l10n/pl.json +++ b/apps/settings/l10n/pl.json @@ -418,6 +418,12 @@ "Excluded groups" : "Wyłączone grupy", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Gdy grupy są zaznaczone lub wykluczone, stosowana jest następująca logika w celu określenia, czy dla konta ma być wymuszona 2FA:. Jeśli nie wybrano żadnych grup, 2FA jest włączone dla wszystkich, z wyjątkiem członków wykluczonych grup. Jeśli grupy są wybrane, 2FA jest włączone dla wszystkich ich członków. Jeśli konto należy zarówno do grupy wybranej, jak i wykluczonej, priorytet ma grupa wybrana i 2FA jest wymuszane.", "Save changes" : "Zapisz zmiany", + "Choose Deploy Daemon for {appName}" : "Wybierz demona wdrożeniowego dla {appName}", + "Default" : "Domyślny", + "Registered Deploy daemons list" : "Lista zarejestrowanych demonów wdrożeniowych", + "No Deploy daemons configured" : "Brak skonfigurowanych demonów wdrożeniowych", + "Register a custom one or setup from available templates" : "Zarejestruj własnego lub skonfiguruj z dostępnych szablonów", + "Manage Deploy daemons" : "Zarządzaj demonami wdrożeniowymi", "Show details for {appName} app" : "Pokaż szczegóły aplikacji {appName}", "Update to {update}" : "Zaktualizuj do {update}", "Remove" : "Usuń", diff --git a/apps/settings/l10n/pt_BR.js b/apps/settings/l10n/pt_BR.js index ad08ddf44aa..b6bd95b29da 100644 --- a/apps/settings/l10n/pt_BR.js +++ b/apps/settings/l10n/pt_BR.js @@ -345,6 +345,7 @@ OC.L10N.register( "Unified task processing" : "Processamento unificado de tarefas", "AI tasks can be implemented by different apps. Here you can set which app should be used for which task." : "As tarefas de IA podem ser implementadas por aplicativos diferentes. Aqui você pode definir qual aplicativo deve ser usado para qual tarefa.", "Allow AI usage for guest users" : "Permitir o uso de IA para usuários convidados", + "Provider for Task types" : "Provedor para tipos de tarefas", "Enable" : "Ativar", "None of your currently installed apps provide Task processing functionality" : "Nenhum dos seus aplicativos instalados atualmente oferece funcionalidade de processamento de Tarefas", "Machine translation" : "Traduções automáticas", @@ -419,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Grupos excluídos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Quando os grupos são selecionados/excluídos, eles usam a seguinte lógica para determinar se uma conta tem a 2FA aplicada: Se nenhum grupo for selecionado, a 2FA será habilitada para todos, exceto os membros dos grupos excluídos. Se grupos forem selecionados, a 2FA será habilitada para todos os membros deles. Se uma conta estiver em um grupo selecionado e excluído, o selecionado terá precedência e a 2FA será aplicada.", "Save changes" : "Salvar alterações", + "Choose Deploy Daemon for {appName}" : "Escolha Implantar Daemon para{appName}", + "Default" : "Padrão", + "Registered Deploy daemons list" : "Lista de Deploy daemons registrados", + "No Deploy daemons configured" : "Nenhum Deploy daemon configurado", + "Register a custom one or setup from available templates" : "Registre um personalizado ou configure a partir dos modelos disponíveis", + "Manage Deploy daemons" : "Gerenciar daemons de implantação", "Show details for {appName} app" : "Mostrar detalhes do aplicativo {appName}", "Update to {update}" : "Atualizar para {update}", "Remove" : "Excluir", @@ -620,6 +627,7 @@ OC.L10N.register( "Your biography. Markdown is supported." : "Sua biografia. Markdown é suportado.", "Unable to update date of birth" : "Não foi possível atualizar a data de nascimento", "Enter your date of birth" : "Digite sua data de nascimento", + "Bluesky handle" : "Alça Bluesky", "You are using {s}{usage}{/s}" : "Você está usando {s}{usage}{/s}", "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Você está usando {s}{usage}{/s} de {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Você é membro dos seguintes grupos:", diff --git a/apps/settings/l10n/pt_BR.json b/apps/settings/l10n/pt_BR.json index 025a6d0b761..9742ef9df72 100644 --- a/apps/settings/l10n/pt_BR.json +++ b/apps/settings/l10n/pt_BR.json @@ -343,6 +343,7 @@ "Unified task processing" : "Processamento unificado de tarefas", "AI tasks can be implemented by different apps. Here you can set which app should be used for which task." : "As tarefas de IA podem ser implementadas por aplicativos diferentes. Aqui você pode definir qual aplicativo deve ser usado para qual tarefa.", "Allow AI usage for guest users" : "Permitir o uso de IA para usuários convidados", + "Provider for Task types" : "Provedor para tipos de tarefas", "Enable" : "Ativar", "None of your currently installed apps provide Task processing functionality" : "Nenhum dos seus aplicativos instalados atualmente oferece funcionalidade de processamento de Tarefas", "Machine translation" : "Traduções automáticas", @@ -417,6 +418,12 @@ "Excluded groups" : "Grupos excluídos", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Quando os grupos são selecionados/excluídos, eles usam a seguinte lógica para determinar se uma conta tem a 2FA aplicada: Se nenhum grupo for selecionado, a 2FA será habilitada para todos, exceto os membros dos grupos excluídos. Se grupos forem selecionados, a 2FA será habilitada para todos os membros deles. Se uma conta estiver em um grupo selecionado e excluído, o selecionado terá precedência e a 2FA será aplicada.", "Save changes" : "Salvar alterações", + "Choose Deploy Daemon for {appName}" : "Escolha Implantar Daemon para{appName}", + "Default" : "Padrão", + "Registered Deploy daemons list" : "Lista de Deploy daemons registrados", + "No Deploy daemons configured" : "Nenhum Deploy daemon configurado", + "Register a custom one or setup from available templates" : "Registre um personalizado ou configure a partir dos modelos disponíveis", + "Manage Deploy daemons" : "Gerenciar daemons de implantação", "Show details for {appName} app" : "Mostrar detalhes do aplicativo {appName}", "Update to {update}" : "Atualizar para {update}", "Remove" : "Excluir", @@ -618,6 +625,7 @@ "Your biography. Markdown is supported." : "Sua biografia. Markdown é suportado.", "Unable to update date of birth" : "Não foi possível atualizar a data de nascimento", "Enter your date of birth" : "Digite sua data de nascimento", + "Bluesky handle" : "Alça Bluesky", "You are using {s}{usage}{/s}" : "Você está usando {s}{usage}{/s}", "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Você está usando {s}{usage}{/s} de {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Você é membro dos seguintes grupos:", diff --git a/apps/settings/l10n/pt_PT.js b/apps/settings/l10n/pt_PT.js index 820a2faae2a..94659f07e89 100644 --- a/apps/settings/l10n/pt_PT.js +++ b/apps/settings/l10n/pt_PT.js @@ -119,6 +119,7 @@ OC.L10N.register( "This text will be shown on the public link upload page when the file list is hidden." : "Este texto será exibido na página de carregamento de ligações públicas quando a lista de ficheiros estiver oculta. ", "Limit to groups" : "Limitado a grupos", "Save changes" : "Guardar alterações", + "Default" : "Predefinido", "Update to {update}" : "Atualizar para {update}", "Remove" : "Remover", "Featured" : "Destacado", diff --git a/apps/settings/l10n/pt_PT.json b/apps/settings/l10n/pt_PT.json index 981da1a36b8..b7a23f72457 100644 --- a/apps/settings/l10n/pt_PT.json +++ b/apps/settings/l10n/pt_PT.json @@ -117,6 +117,7 @@ "This text will be shown on the public link upload page when the file list is hidden." : "Este texto será exibido na página de carregamento de ligações públicas quando a lista de ficheiros estiver oculta. ", "Limit to groups" : "Limitado a grupos", "Save changes" : "Guardar alterações", + "Default" : "Predefinido", "Update to {update}" : "Atualizar para {update}", "Remove" : "Remover", "Featured" : "Destacado", diff --git a/apps/settings/l10n/ro.js b/apps/settings/l10n/ro.js index 9e68b25739b..2dff3b9651f 100644 --- a/apps/settings/l10n/ro.js +++ b/apps/settings/l10n/ro.js @@ -138,6 +138,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Autentificarea cu doi factori nu este impusă pentru membrii următoarelor grupuri.", "Excluded groups" : "Grupuri excluse", "Save changes" : "Salvează modificările", + "Default" : "Implicită", "Remove" : "Șterge", "Featured" : "Recomandată", "Icon" : "Pictogramă", diff --git a/apps/settings/l10n/ro.json b/apps/settings/l10n/ro.json index 2980437577f..171760808b0 100644 --- a/apps/settings/l10n/ro.json +++ b/apps/settings/l10n/ro.json @@ -136,6 +136,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Autentificarea cu doi factori nu este impusă pentru membrii următoarelor grupuri.", "Excluded groups" : "Grupuri excluse", "Save changes" : "Salvează modificările", + "Default" : "Implicită", "Remove" : "Șterge", "Featured" : "Recomandată", "Icon" : "Pictogramă", diff --git a/apps/settings/l10n/ru.js b/apps/settings/l10n/ru.js index 41c3fe63f22..4b9adc59ce8 100644 --- a/apps/settings/l10n/ru.js +++ b/apps/settings/l10n/ru.js @@ -416,6 +416,10 @@ OC.L10N.register( "Excluded groups" : "Группы без требования использования двухфакторной аутентификации", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Если выбрано включение или отключение использования двухфакторной проверки подлинности для групп, то для определения, требуется ли от пользователя использовать её, применяются следующие правила: \\n\n - если группы не включены в список, то двухфакторная проверка включена для всех их участников, кроме тех, кто также состоит в группах, проверка для которых отключена;\n - если группы включены в список, то двухфакторная проверка включена для всех участников таких групп;\n- если учётная запись состоит одновременно и в группе, проверка для которой включена и группе, проверка для которой отключена, то приоритет получает использование двухфакторной проверки.", "Save changes" : "Сохранить изменения", + "Default" : "По умолчанию", + "Registered Deploy daemons list" : "Список зарегистрированных служб развертывания", + "No Deploy daemons configured" : "Службы развертывания не настроены", + "Register a custom one or setup from available templates" : "Зарегистрируйте индивидуальный шаблон или настройте его из доступных шаблонов", "Show details for {appName} app" : "Подробные сведения о приложении «{appName}»", "Update to {update}" : "Обновить до {update}", "Remove" : "Удалить", diff --git a/apps/settings/l10n/ru.json b/apps/settings/l10n/ru.json index 31c34892dc9..a3179f6b2b9 100644 --- a/apps/settings/l10n/ru.json +++ b/apps/settings/l10n/ru.json @@ -414,6 +414,10 @@ "Excluded groups" : "Группы без требования использования двухфакторной аутентификации", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Если выбрано включение или отключение использования двухфакторной проверки подлинности для групп, то для определения, требуется ли от пользователя использовать её, применяются следующие правила: \\n\n - если группы не включены в список, то двухфакторная проверка включена для всех их участников, кроме тех, кто также состоит в группах, проверка для которых отключена;\n - если группы включены в список, то двухфакторная проверка включена для всех участников таких групп;\n- если учётная запись состоит одновременно и в группе, проверка для которой включена и группе, проверка для которой отключена, то приоритет получает использование двухфакторной проверки.", "Save changes" : "Сохранить изменения", + "Default" : "По умолчанию", + "Registered Deploy daemons list" : "Список зарегистрированных служб развертывания", + "No Deploy daemons configured" : "Службы развертывания не настроены", + "Register a custom one or setup from available templates" : "Зарегистрируйте индивидуальный шаблон или настройте его из доступных шаблонов", "Show details for {appName} app" : "Подробные сведения о приложении «{appName}»", "Update to {update}" : "Обновить до {update}", "Remove" : "Удалить", diff --git a/apps/settings/l10n/sc.js b/apps/settings/l10n/sc.js index 2bc5c774434..33098fa59be 100644 --- a/apps/settings/l10n/sc.js +++ b/apps/settings/l10n/sc.js @@ -144,6 +144,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "S'autenticatzione a duos fatores no est cunfigurada pro cumponentes de is grupos in fatu.", "Excluded groups" : "Grupos esclùdidos", "Save changes" : "Sarva càmbios", + "Default" : "Predefinidu", "Update to {update}" : "Agiorna a {update}", "Remove" : "Boga", "Featured" : "In evidèntzia", diff --git a/apps/settings/l10n/sc.json b/apps/settings/l10n/sc.json index a75db28c4ab..d06c0d55147 100644 --- a/apps/settings/l10n/sc.json +++ b/apps/settings/l10n/sc.json @@ -142,6 +142,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "S'autenticatzione a duos fatores no est cunfigurada pro cumponentes de is grupos in fatu.", "Excluded groups" : "Grupos esclùdidos", "Save changes" : "Sarva càmbios", + "Default" : "Predefinidu", "Update to {update}" : "Agiorna a {update}", "Remove" : "Boga", "Featured" : "In evidèntzia", diff --git a/apps/settings/l10n/sk.js b/apps/settings/l10n/sk.js index 5c53adc2ca7..c3ca97023c3 100644 --- a/apps/settings/l10n/sk.js +++ b/apps/settings/l10n/sk.js @@ -408,6 +408,10 @@ OC.L10N.register( "Excluded groups" : "Vynechané skupiny", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Pokiaľ sú skupiny vybraté/vynechané, je pre zisťovanie či je účtu vynútené dvojfaktorové (2FA) overovanie použitá nasledovná logika: Ak nie sú vybraté žiadne skupiny, je 2FA zapnuté pre všetkých okrem členov vynechaných skupín. Ak sú nejaké skupiny vybraté, je 2FA zapnuté pre všetkých jej členov. Ak je účet členom ako vybratej, tak aj vynechanej skupiny, potom má vybratá skupina prednosť a 2FA je vynútené.", "Save changes" : "Uložiť zmeny", + "Default" : "Predvolené", + "Registered Deploy daemons list" : "Zoznam registrovaných démonov nasadenia", + "No Deploy daemons configured" : "Neboli nastavené žiadne Démony nasadeia", + "Register a custom one or setup from available templates" : "Zaregistrujte si vlastný alebo ho nastavte z dostupných šablón", "Show details for {appName} app" : "Zobraziť podrobnosti o aplikácii {appName}", "Update to {update}" : "Aktualizovať na {update}", "Remove" : "Odstrániť", diff --git a/apps/settings/l10n/sk.json b/apps/settings/l10n/sk.json index 50f324bc1c5..857d989adb7 100644 --- a/apps/settings/l10n/sk.json +++ b/apps/settings/l10n/sk.json @@ -406,6 +406,10 @@ "Excluded groups" : "Vynechané skupiny", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Pokiaľ sú skupiny vybraté/vynechané, je pre zisťovanie či je účtu vynútené dvojfaktorové (2FA) overovanie použitá nasledovná logika: Ak nie sú vybraté žiadne skupiny, je 2FA zapnuté pre všetkých okrem členov vynechaných skupín. Ak sú nejaké skupiny vybraté, je 2FA zapnuté pre všetkých jej členov. Ak je účet členom ako vybratej, tak aj vynechanej skupiny, potom má vybratá skupina prednosť a 2FA je vynútené.", "Save changes" : "Uložiť zmeny", + "Default" : "Predvolené", + "Registered Deploy daemons list" : "Zoznam registrovaných démonov nasadenia", + "No Deploy daemons configured" : "Neboli nastavené žiadne Démony nasadeia", + "Register a custom one or setup from available templates" : "Zaregistrujte si vlastný alebo ho nastavte z dostupných šablón", "Show details for {appName} app" : "Zobraziť podrobnosti o aplikácii {appName}", "Update to {update}" : "Aktualizovať na {update}", "Remove" : "Odstrániť", diff --git a/apps/settings/l10n/sl.js b/apps/settings/l10n/sl.js index e96e235de9f..ab2341a6986 100644 --- a/apps/settings/l10n/sl.js +++ b/apps/settings/l10n/sl.js @@ -286,6 +286,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Dvo-stopenjsko overjanje ni obvezno za člane navedenih skupin.", "Excluded groups" : "Izločene skupine", "Save changes" : "Shrani spremembe", + "Default" : "Privzeto", "Show details for {appName} app" : "Pokaži podrobnosti programa {appName}", "Update to {update}" : "Posodobi na različico {update}", "Remove" : "Odstrani", diff --git a/apps/settings/l10n/sl.json b/apps/settings/l10n/sl.json index 959d567c3f4..a5080347e97 100644 --- a/apps/settings/l10n/sl.json +++ b/apps/settings/l10n/sl.json @@ -284,6 +284,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Dvo-stopenjsko overjanje ni obvezno za člane navedenih skupin.", "Excluded groups" : "Izločene skupine", "Save changes" : "Shrani spremembe", + "Default" : "Privzeto", "Show details for {appName} app" : "Pokaži podrobnosti programa {appName}", "Update to {update}" : "Posodobi na različico {update}", "Remove" : "Odstrani", diff --git a/apps/settings/l10n/sq.js b/apps/settings/l10n/sq.js index a574b182c20..ff8ff584dac 100644 --- a/apps/settings/l10n/sq.js +++ b/apps/settings/l10n/sq.js @@ -88,6 +88,7 @@ OC.L10N.register( "This text will be shown on the public link upload page when the file list is hidden." : "Ky tekst do të shfaqet në linkun publik të faqes së ngarkuar kur lista e skedarit të jetë e fshehur.", "Limit to groups" : "Kufizo grupet", "Save changes" : "Ruaj ndryshimet", + "Default" : "Paraprake", "Remove" : "Hiqe", "Featured" : "I paraqitur", "Icon" : "Ikonë", diff --git a/apps/settings/l10n/sq.json b/apps/settings/l10n/sq.json index 1cbea7615e5..74eda7e5f28 100644 --- a/apps/settings/l10n/sq.json +++ b/apps/settings/l10n/sq.json @@ -86,6 +86,7 @@ "This text will be shown on the public link upload page when the file list is hidden." : "Ky tekst do të shfaqet në linkun publik të faqes së ngarkuar kur lista e skedarit të jetë e fshehur.", "Limit to groups" : "Kufizo grupet", "Save changes" : "Ruaj ndryshimet", + "Default" : "Paraprake", "Remove" : "Hiqe", "Featured" : "I paraqitur", "Icon" : "Ikonë", diff --git a/apps/settings/l10n/sr.js b/apps/settings/l10n/sr.js index 393864ba204..19a35ecb06f 100644 --- a/apps/settings/l10n/sr.js +++ b/apps/settings/l10n/sr.js @@ -345,6 +345,7 @@ OC.L10N.register( "Unified task processing" : "Обједињена обрада задатака", "AI tasks can be implemented by different apps. Here you can set which app should be used for which task." : "AI задатке могу да имплементирају разне апликације. Овде можете да подесите која ће се користити за који задатак.", "Allow AI usage for guest users" : "Дозволи да корисници гости користе AI", + "Provider for Task types" : "Пружалац услуге за Типове задатака", "Enable" : "Укључи", "None of your currently installed apps provide Task processing functionality" : "Ниједна од ваших тренутно инсталираних апликација не пружа функционалност обраде задатака", "Machine translation" : "Машинско превођење", @@ -371,6 +372,8 @@ OC.L10N.register( "Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Корисници ће ипак моћи да направе слику екрана или да сниме екран. Ово не представља дефинитивну заштиту.", "Allow users to share via link and emails" : "Дозволи корисницима да деле путем линка и и-мејлова", "Allow public uploads" : "Дозволи јавна отпремања", + "Allow public shares to be added to other clouds by federation." : "Дозволи да се јавна дељења здруживањем додају на остале облаке.", + "This will add share permissions to all newly created link shares." : "Ово ће додати дозволе дељења свим новокреираним дељењима линком.", "Always ask for a password" : "Увек питај за лозинку", "Enforce password protection" : "Захтевај заштиту лозинком", "Exclude groups from password requirements" : "Изузми групе из захтева за лозинком", @@ -394,6 +397,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Подразумевано време трајања удаљених дељења у данима", "Expire remote shares after x days" : "Нека удаљена дељења истекну након х дана", "Set default expiration date for shares via link or mail" : "Постави подразумевано време истека за дељења путем линка или мејла", + "Enforce expiration date for link or mail shares" : "Форсирај датум истека за дељења линком или мејлом", "Default expiration time of shares in days" : "Подразумевано време трајања дељења у данима", "Privacy settings for sharing" : "Поставке приватности за дељење", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Омогући аутоматско довршавање имена налога у дијалогу дељења и дозволи приступ системском адресару", @@ -416,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Искључене групе", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Када се групе означе/искључе, користи се следећа логика да се закључи да ли се за налог захтева 2FA: ако није одабрана ниједна група, 2FA је укључен свима осим члановима искључених група. Ако има одабраних група, 2FA је укључен само њиховим члановима. Ако је налог у исто време у одабраној и у искљученој групи, одабрана група има предност и 2FA се захтева.", "Save changes" : "Сними измене", + "Choose Deploy Daemon for {appName}" : "Изабери Даемон за постављање за {appName}", + "Default" : "Подразумевано", + "Registered Deploy daemons list" : "Листа регистрованих Даемона за постављање", + "No Deploy daemons configured" : "Није подешен ниједан Даемон за постављање", + "Register a custom one or setup from available templates" : "Региструјте произвољни или подесите неки према доступном шаблону", + "Manage Deploy daemons" : "Управљај Даемонима за постављање", "Show details for {appName} app" : "Прикажи детаље апликације {appName}", "Update to {update}" : "Ажурирај на {update}", "Remove" : "Уклони", @@ -617,6 +627,7 @@ OC.L10N.register( "Your biography. Markdown is supported." : "Ваша биографија. Подржава се употреба Markdown означавања.", "Unable to update date of birth" : "Није успело ажурирање датума рођења", "Enter your date of birth" : "Унесите датум свог рођења", + "Bluesky handle" : "Bluesky ручка", "You are using {s}{usage}{/s}" : "Користите {s}{usage}{/s}", "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Користите {s}{usage}{/s} од {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Члан сте следећих група:", @@ -808,6 +819,7 @@ OC.L10N.register( "Pronouns" : "Заменице", "Role" : "Улога", "X (formerly Twitter)" : "X (бивши Twitter)", + "Bluesky" : "Bluesky", "Website" : "Веб сајт", "Profile visibility" : "Видљивост профила", "Locale" : "Локалитет", diff --git a/apps/settings/l10n/sr.json b/apps/settings/l10n/sr.json index e4cac6921fc..507cd5e5c41 100644 --- a/apps/settings/l10n/sr.json +++ b/apps/settings/l10n/sr.json @@ -343,6 +343,7 @@ "Unified task processing" : "Обједињена обрада задатака", "AI tasks can be implemented by different apps. Here you can set which app should be used for which task." : "AI задатке могу да имплементирају разне апликације. Овде можете да подесите која ће се користити за који задатак.", "Allow AI usage for guest users" : "Дозволи да корисници гости користе AI", + "Provider for Task types" : "Пружалац услуге за Типове задатака", "Enable" : "Укључи", "None of your currently installed apps provide Task processing functionality" : "Ниједна од ваших тренутно инсталираних апликација не пружа функционалност обраде задатака", "Machine translation" : "Машинско превођење", @@ -369,6 +370,8 @@ "Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Корисници ће ипак моћи да направе слику екрана или да сниме екран. Ово не представља дефинитивну заштиту.", "Allow users to share via link and emails" : "Дозволи корисницима да деле путем линка и и-мејлова", "Allow public uploads" : "Дозволи јавна отпремања", + "Allow public shares to be added to other clouds by federation." : "Дозволи да се јавна дељења здруживањем додају на остале облаке.", + "This will add share permissions to all newly created link shares." : "Ово ће додати дозволе дељења свим новокреираним дељењима линком.", "Always ask for a password" : "Увек питај за лозинку", "Enforce password protection" : "Захтевај заштиту лозинком", "Exclude groups from password requirements" : "Изузми групе из захтева за лозинком", @@ -392,6 +395,7 @@ "Default expiration time of remote shares in days" : "Подразумевано време трајања удаљених дељења у данима", "Expire remote shares after x days" : "Нека удаљена дељења истекну након х дана", "Set default expiration date for shares via link or mail" : "Постави подразумевано време истека за дељења путем линка или мејла", + "Enforce expiration date for link or mail shares" : "Форсирај датум истека за дељења линком или мејлом", "Default expiration time of shares in days" : "Подразумевано време трајања дељења у данима", "Privacy settings for sharing" : "Поставке приватности за дељење", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Омогући аутоматско довршавање имена налога у дијалогу дељења и дозволи приступ системском адресару", @@ -414,6 +418,12 @@ "Excluded groups" : "Искључене групе", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Када се групе означе/искључе, користи се следећа логика да се закључи да ли се за налог захтева 2FA: ако није одабрана ниједна група, 2FA је укључен свима осим члановима искључених група. Ако има одабраних група, 2FA је укључен само њиховим члановима. Ако је налог у исто време у одабраној и у искљученој групи, одабрана група има предност и 2FA се захтева.", "Save changes" : "Сними измене", + "Choose Deploy Daemon for {appName}" : "Изабери Даемон за постављање за {appName}", + "Default" : "Подразумевано", + "Registered Deploy daemons list" : "Листа регистрованих Даемона за постављање", + "No Deploy daemons configured" : "Није подешен ниједан Даемон за постављање", + "Register a custom one or setup from available templates" : "Региструјте произвољни или подесите неки према доступном шаблону", + "Manage Deploy daemons" : "Управљај Даемонима за постављање", "Show details for {appName} app" : "Прикажи детаље апликације {appName}", "Update to {update}" : "Ажурирај на {update}", "Remove" : "Уклони", @@ -615,6 +625,7 @@ "Your biography. Markdown is supported." : "Ваша биографија. Подржава се употреба Markdown означавања.", "Unable to update date of birth" : "Није успело ажурирање датума рођења", "Enter your date of birth" : "Унесите датум свог рођења", + "Bluesky handle" : "Bluesky ручка", "You are using {s}{usage}{/s}" : "Користите {s}{usage}{/s}", "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Користите {s}{usage}{/s} од {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Члан сте следећих група:", @@ -806,6 +817,7 @@ "Pronouns" : "Заменице", "Role" : "Улога", "X (formerly Twitter)" : "X (бивши Twitter)", + "Bluesky" : "Bluesky", "Website" : "Веб сајт", "Profile visibility" : "Видљивост профила", "Locale" : "Локалитет", diff --git a/apps/settings/l10n/sv.js b/apps/settings/l10n/sv.js index 854feb78d73..0fd716e6bff 100644 --- a/apps/settings/l10n/sv.js +++ b/apps/settings/l10n/sv.js @@ -287,6 +287,7 @@ OC.L10N.register( "Two-factor authentication is not enforced for members of the following groups." : "Tvåfaktorsautentisering är inte påtvingad för medlemmar i följande grupper.", "Excluded groups" : "Exkluderade grupper", "Save changes" : "Spara ändringar", + "Default" : "Förvald", "Show details for {appName} app" : "Visa detaljer för appen {appName}", "Update to {update}" : "Uppdatera till {update}", "Remove" : "Ta bort", diff --git a/apps/settings/l10n/sv.json b/apps/settings/l10n/sv.json index 92f0307ce10..97ecfe9c09a 100644 --- a/apps/settings/l10n/sv.json +++ b/apps/settings/l10n/sv.json @@ -285,6 +285,7 @@ "Two-factor authentication is not enforced for members of the following groups." : "Tvåfaktorsautentisering är inte påtvingad för medlemmar i följande grupper.", "Excluded groups" : "Exkluderade grupper", "Save changes" : "Spara ändringar", + "Default" : "Förvald", "Show details for {appName} app" : "Visa detaljer för appen {appName}", "Update to {update}" : "Uppdatera till {update}", "Remove" : "Ta bort", diff --git a/apps/settings/l10n/tr.js b/apps/settings/l10n/tr.js index ff6ab78ce80..68ac2277ece 100644 --- a/apps/settings/l10n/tr.js +++ b/apps/settings/l10n/tr.js @@ -418,6 +418,10 @@ OC.L10N.register( "Excluded groups" : "Uygulanmayacak gruplar", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Uygulanan ya da uygulanmayan gruplar belirtildiğinde, bir hesap için iki adımlı doğrulamanın zorunlu kılınıp kılınmayacağına şu şekilde karar verilir. Herhangi bir grup belirtilmemiş ise, uygulanmayan grupların üyeleri dışındaki tüm üyeler için iki adımlı doğrulama kullanılır. Belirtilmiş gruplar varsa, uygulanan bu grupların üyeleri için iki adımlı doğrulama kullanılır. Bir hesabın hem uygulanan hem de uygulanmayan gruplarda üyeliği varsa, uygulanan grupların önceliği vardır ve iki adımlı doğrulama zorunlu kılınır.", "Save changes" : "Değişiklikleri kaydet", + "Default" : "Varsayılan", + "Registered Deploy daemons list" : "Kaydedilmiş dağıtım arka plan işlemleri listesi", + "No Deploy daemons configured" : "Herhangi bir dağıtım arka plan işlemi yapılandırılmamış", + "Register a custom one or setup from available templates" : "Özel bir tane kaydedin ya da kullanılabilecek kalıplardan kurun", "Show details for {appName} app" : "{appName} uygulamasının ayrıntılarını görüntüle", "Update to {update}" : "{update} sürümüne güncelle", "Remove" : "Kaldır", diff --git a/apps/settings/l10n/tr.json b/apps/settings/l10n/tr.json index f1f2a3a9ddb..58be80541ef 100644 --- a/apps/settings/l10n/tr.json +++ b/apps/settings/l10n/tr.json @@ -416,6 +416,10 @@ "Excluded groups" : "Uygulanmayacak gruplar", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Uygulanan ya da uygulanmayan gruplar belirtildiğinde, bir hesap için iki adımlı doğrulamanın zorunlu kılınıp kılınmayacağına şu şekilde karar verilir. Herhangi bir grup belirtilmemiş ise, uygulanmayan grupların üyeleri dışındaki tüm üyeler için iki adımlı doğrulama kullanılır. Belirtilmiş gruplar varsa, uygulanan bu grupların üyeleri için iki adımlı doğrulama kullanılır. Bir hesabın hem uygulanan hem de uygulanmayan gruplarda üyeliği varsa, uygulanan grupların önceliği vardır ve iki adımlı doğrulama zorunlu kılınır.", "Save changes" : "Değişiklikleri kaydet", + "Default" : "Varsayılan", + "Registered Deploy daemons list" : "Kaydedilmiş dağıtım arka plan işlemleri listesi", + "No Deploy daemons configured" : "Herhangi bir dağıtım arka plan işlemi yapılandırılmamış", + "Register a custom one or setup from available templates" : "Özel bir tane kaydedin ya da kullanılabilecek kalıplardan kurun", "Show details for {appName} app" : "{appName} uygulamasının ayrıntılarını görüntüle", "Update to {update}" : "{update} sürümüne güncelle", "Remove" : "Kaldır", diff --git a/apps/settings/l10n/ug.js b/apps/settings/l10n/ug.js index 55557162fec..f32989b1892 100644 --- a/apps/settings/l10n/ug.js +++ b/apps/settings/l10n/ug.js @@ -379,6 +379,10 @@ OC.L10N.register( "Excluded groups" : "چىقىرىۋېتىلگەن گۇرۇپپىلار", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "گۇرۇپپىلار تاللانغان / چىقىرىۋېتىلگەندە ، ئۇلار تۆۋەندىكى لوگىكا ئارقىلىق ھېساباتنىڭ 2FA ئىجرا قىلىنغان-قىلىنمىغانلىقىنى ئېنىقلايدۇ: ئەگەر گۇرۇپپا تاللانمىسا ، چىقىرىۋېتىلگەن گۇرۇپپا ئەزالىرىدىن باشقا ھەممە ئادەم ئۈچۈن 2FA قوزغىتىلىدۇ. ئەگەر گۇرۇپپىلار تاللانسا ، بۇلارنىڭ بارلىق ئەزالىرى ئۈچۈن 2FA قوزغىتىلغان. ئەگەر ھېسابات تاللانغان ۋە چىقىرىۋېتىلگەن گۇرۇپپىدا بولسا ، تاللانغانلار ئالدىنقى ئورۇنغا قويىدۇ ۋە 2FA ئىجرا قىلىنىدۇ.", "Save changes" : "ئۆزگەرتىشلەرنى ساقلاڭ", + "Default" : "كۆڭۈلدىكى", + "Registered Deploy daemons list" : "تىزىملاتقان دامونلار تىزىملىكى", + "No Deploy daemons configured" : "ھېچقانداق ئورۇنلاشتۇرۇش لايىھىسى سەپلەنمىگەن", + "Register a custom one or setup from available templates" : "ئىختىيارى قېلىپ ياكى تىزىملىتىڭ", "Show details for {appName} app" : "{appName} دېتالىنىڭ تەپسىلاتلىرىنى كۆرسىتىڭ", "Update to {update}" : "{update} غا يېڭىلاش", "Remove" : "چىقىرىۋەت", diff --git a/apps/settings/l10n/ug.json b/apps/settings/l10n/ug.json index 8ba3fbcfa23..ef6c22c0b50 100644 --- a/apps/settings/l10n/ug.json +++ b/apps/settings/l10n/ug.json @@ -377,6 +377,10 @@ "Excluded groups" : "چىقىرىۋېتىلگەن گۇرۇپپىلار", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "گۇرۇپپىلار تاللانغان / چىقىرىۋېتىلگەندە ، ئۇلار تۆۋەندىكى لوگىكا ئارقىلىق ھېساباتنىڭ 2FA ئىجرا قىلىنغان-قىلىنمىغانلىقىنى ئېنىقلايدۇ: ئەگەر گۇرۇپپا تاللانمىسا ، چىقىرىۋېتىلگەن گۇرۇپپا ئەزالىرىدىن باشقا ھەممە ئادەم ئۈچۈن 2FA قوزغىتىلىدۇ. ئەگەر گۇرۇپپىلار تاللانسا ، بۇلارنىڭ بارلىق ئەزالىرى ئۈچۈن 2FA قوزغىتىلغان. ئەگەر ھېسابات تاللانغان ۋە چىقىرىۋېتىلگەن گۇرۇپپىدا بولسا ، تاللانغانلار ئالدىنقى ئورۇنغا قويىدۇ ۋە 2FA ئىجرا قىلىنىدۇ.", "Save changes" : "ئۆزگەرتىشلەرنى ساقلاڭ", + "Default" : "كۆڭۈلدىكى", + "Registered Deploy daemons list" : "تىزىملاتقان دامونلار تىزىملىكى", + "No Deploy daemons configured" : "ھېچقانداق ئورۇنلاشتۇرۇش لايىھىسى سەپلەنمىگەن", + "Register a custom one or setup from available templates" : "ئىختىيارى قېلىپ ياكى تىزىملىتىڭ", "Show details for {appName} app" : "{appName} دېتالىنىڭ تەپسىلاتلىرىنى كۆرسىتىڭ", "Update to {update}" : "{update} غا يېڭىلاش", "Remove" : "چىقىرىۋەت", diff --git a/apps/settings/l10n/uk.js b/apps/settings/l10n/uk.js index 9de2885a351..d3888dd835a 100644 --- a/apps/settings/l10n/uk.js +++ b/apps/settings/l10n/uk.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "Виключені групи", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Якщо вибрано/виключено групи, то застосовується така логіка визначення, чи до користувача застосовується двофакторна авторизація: якщо не вибрано жодної групи, двофакторну авторизацію ввімкнено для всіх, крім учасників виключених груп. Якщо вибрано групи, для всіх учасників увімкнено двофакторну авторизацію. Якщо користувач одночасно входить до вибраної групи та виключеної групи, то вибрана група має пріоритет і в такому випадку до цього користувача застосовується двофакторна авторизація.", "Save changes" : "Зберегти зміни", + "Choose Deploy Daemon for {appName}" : "Виберіть Deploy Daemon для {appName}", + "Default" : "Типово", + "Registered Deploy daemons list" : "Список зареєстрованих демонів розгортання", + "No Deploy daemons configured" : "Не налаштовано демонів розгортання", + "Register a custom one or setup from available templates" : "Зареєструвати власний або налаштувати з доступних шаблонів", + "Manage Deploy daemons" : "Керувати демонами розгортання", "Show details for {appName} app" : "Показати деталі для застосунку {appName}", "Update to {update}" : "Оновити до {update}", "Remove" : "Вилучити", diff --git a/apps/settings/l10n/uk.json b/apps/settings/l10n/uk.json index ba1fc378b08..bbdbde52932 100644 --- a/apps/settings/l10n/uk.json +++ b/apps/settings/l10n/uk.json @@ -418,6 +418,12 @@ "Excluded groups" : "Виключені групи", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Якщо вибрано/виключено групи, то застосовується така логіка визначення, чи до користувача застосовується двофакторна авторизація: якщо не вибрано жодної групи, двофакторну авторизацію ввімкнено для всіх, крім учасників виключених груп. Якщо вибрано групи, для всіх учасників увімкнено двофакторну авторизацію. Якщо користувач одночасно входить до вибраної групи та виключеної групи, то вибрана група має пріоритет і в такому випадку до цього користувача застосовується двофакторна авторизація.", "Save changes" : "Зберегти зміни", + "Choose Deploy Daemon for {appName}" : "Виберіть Deploy Daemon для {appName}", + "Default" : "Типово", + "Registered Deploy daemons list" : "Список зареєстрованих демонів розгортання", + "No Deploy daemons configured" : "Не налаштовано демонів розгортання", + "Register a custom one or setup from available templates" : "Зареєструвати власний або налаштувати з доступних шаблонів", + "Manage Deploy daemons" : "Керувати демонами розгортання", "Show details for {appName} app" : "Показати деталі для застосунку {appName}", "Update to {update}" : "Оновити до {update}", "Remove" : "Вилучити", diff --git a/apps/settings/l10n/zh_CN.js b/apps/settings/l10n/zh_CN.js index c4db099cbe6..0ada283f2ac 100644 --- a/apps/settings/l10n/zh_CN.js +++ b/apps/settings/l10n/zh_CN.js @@ -419,6 +419,10 @@ OC.L10N.register( "Excluded groups" : "排除群组", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "选择/排除 组时,它们使用以下逻辑来确定帐户是否强制执行 2FA:如果未选择任何组,则将为除排除组的成员之外的所有人启用 2FA。 如果选择组,则会为其中的所有成员启用 2FA。 如果账户同时位于选定组和排除组中,则选定组优先并强制执行 2FA。", "Save changes" : "保存修改", + "Default" : "默认", + "Registered Deploy daemons list" : "已注册的部署守护进程列表", + "No Deploy daemons configured" : "没有部署守护进程配置", + "Register a custom one or setup from available templates" : "注册自定义模板或从可用模板进行安装", "Show details for {appName} app" : "显示应用 {appName} 的详细信息", "Update to {update}" : "更新至 {update}", "Remove" : "移除", diff --git a/apps/settings/l10n/zh_CN.json b/apps/settings/l10n/zh_CN.json index 3526400cf2d..c22a50fefb7 100644 --- a/apps/settings/l10n/zh_CN.json +++ b/apps/settings/l10n/zh_CN.json @@ -417,6 +417,10 @@ "Excluded groups" : "排除群组", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "选择/排除 组时,它们使用以下逻辑来确定帐户是否强制执行 2FA:如果未选择任何组,则将为除排除组的成员之外的所有人启用 2FA。 如果选择组,则会为其中的所有成员启用 2FA。 如果账户同时位于选定组和排除组中,则选定组优先并强制执行 2FA。", "Save changes" : "保存修改", + "Default" : "默认", + "Registered Deploy daemons list" : "已注册的部署守护进程列表", + "No Deploy daemons configured" : "没有部署守护进程配置", + "Register a custom one or setup from available templates" : "注册自定义模板或从可用模板进行安装", "Show details for {appName} app" : "显示应用 {appName} 的详细信息", "Update to {update}" : "更新至 {update}", "Remove" : "移除", diff --git a/apps/settings/l10n/zh_HK.js b/apps/settings/l10n/zh_HK.js index 9e5eff6e9a8..1d19804ea48 100644 --- a/apps/settings/l10n/zh_HK.js +++ b/apps/settings/l10n/zh_HK.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "排除的群組", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "選擇/排除群組時,其使用以下邏輯來確定帳戶是否強制啟用雙因素驗證:如果沒有選擇群組,雙因素驗證對所有除了排除群組以外的成員啟用。若選擇了群組,則雙因素驗證會對所有該群組的成員啟用。如果某個帳戶同時位在選擇與排除的群組,則被選擇的群組優先程度較高,因此會強制啟用雙因素驗證。", "Save changes" : "儲存變更", + "Choose Deploy Daemon for {appName}" : "選擇 {appName} 的部署幕後程式", + "Default" : "默認", + "Registered Deploy daemons list" : "已註冊的部署幕後程式清單", + "No Deploy daemons configured" : "未設定部署幕後程式", + "Register a custom one or setup from available templates" : "從可用範本註冊自訂範本或設定", + "Manage Deploy daemons" : "管理部署幕後程式", "Show details for {appName} app" : "顯示 {appName} 應用程式的詳細資訊", "Update to {update}" : "更新到 {update}", "Remove" : "移除", diff --git a/apps/settings/l10n/zh_HK.json b/apps/settings/l10n/zh_HK.json index 7701813a63d..286202d71ff 100644 --- a/apps/settings/l10n/zh_HK.json +++ b/apps/settings/l10n/zh_HK.json @@ -418,6 +418,12 @@ "Excluded groups" : "排除的群組", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "選擇/排除群組時,其使用以下邏輯來確定帳戶是否強制啟用雙因素驗證:如果沒有選擇群組,雙因素驗證對所有除了排除群組以外的成員啟用。若選擇了群組,則雙因素驗證會對所有該群組的成員啟用。如果某個帳戶同時位在選擇與排除的群組,則被選擇的群組優先程度較高,因此會強制啟用雙因素驗證。", "Save changes" : "儲存變更", + "Choose Deploy Daemon for {appName}" : "選擇 {appName} 的部署幕後程式", + "Default" : "默認", + "Registered Deploy daemons list" : "已註冊的部署幕後程式清單", + "No Deploy daemons configured" : "未設定部署幕後程式", + "Register a custom one or setup from available templates" : "從可用範本註冊自訂範本或設定", + "Manage Deploy daemons" : "管理部署幕後程式", "Show details for {appName} app" : "顯示 {appName} 應用程式的詳細資訊", "Update to {update}" : "更新到 {update}", "Remove" : "移除", diff --git a/apps/settings/l10n/zh_TW.js b/apps/settings/l10n/zh_TW.js index af5d3ffc762..18a6b553e05 100644 --- a/apps/settings/l10n/zh_TW.js +++ b/apps/settings/l10n/zh_TW.js @@ -420,6 +420,12 @@ OC.L10N.register( "Excluded groups" : "排除的群組", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "選取/排除群組時,其使用以下邏輯來確定帳號是否強制啟用雙因素驗證:如果沒有選取群組,雙因素驗證對所有除了排除群組以外的成員啟用。若選取了群組,則雙因素驗證會對所有該群組的成員啟用。如果某個帳號同時位在選取與排除的群組,則被選取的群組優先程度較高,因此會強制啟用雙因素驗證。", "Save changes" : "儲存變更", + "Choose Deploy Daemon for {appName}" : "選擇 {appName} 的部署幕後程式", + "Default" : "預設", + "Registered Deploy daemons list" : "已註冊的部署幕後程式清單", + "No Deploy daemons configured" : "未設定部署幕後程式", + "Register a custom one or setup from available templates" : "從可用範本註冊自訂範本或設定", + "Manage Deploy daemons" : "管理部署幕後程式", "Show details for {appName} app" : "顯示 {appName} 應用程式的詳細資訊", "Update to {update}" : "更新到 {update}", "Remove" : "移除", diff --git a/apps/settings/l10n/zh_TW.json b/apps/settings/l10n/zh_TW.json index da9f3178513..4e1ee7c15e5 100644 --- a/apps/settings/l10n/zh_TW.json +++ b/apps/settings/l10n/zh_TW.json @@ -418,6 +418,12 @@ "Excluded groups" : "排除的群組", "When groups are selected/excluded, they use the following logic to determine if an account has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If an account is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "選取/排除群組時,其使用以下邏輯來確定帳號是否強制啟用雙因素驗證:如果沒有選取群組,雙因素驗證對所有除了排除群組以外的成員啟用。若選取了群組,則雙因素驗證會對所有該群組的成員啟用。如果某個帳號同時位在選取與排除的群組,則被選取的群組優先程度較高,因此會強制啟用雙因素驗證。", "Save changes" : "儲存變更", + "Choose Deploy Daemon for {appName}" : "選擇 {appName} 的部署幕後程式", + "Default" : "預設", + "Registered Deploy daemons list" : "已註冊的部署幕後程式清單", + "No Deploy daemons configured" : "未設定部署幕後程式", + "Register a custom one or setup from available templates" : "從可用範本註冊自訂範本或設定", + "Manage Deploy daemons" : "管理部署幕後程式", "Show details for {appName} app" : "顯示 {appName} 應用程式的詳細資訊", "Update to {update}" : "更新到 {update}", "Remove" : "移除", diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index e038b2a6231..ec5dcdf624d 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -68,7 +68,7 @@ class Sharing implements IDelegatedSettings { 'excludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no'), 'excludeGroupsList' => json_decode($excludedGroups, true) ?? [], 'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext'), - 'enableLinkPasswordByDefault' => $this->getHumanBooleanConfig('core', 'shareapi_enable_link_password_by_default'), + 'enableLinkPasswordByDefault' => $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_DEFAULT), 'defaultPermissions' => (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL), 'defaultInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_internal_expire_date'), 'internalExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'), diff --git a/apps/settings/src/app-types.ts b/apps/settings/src/app-types.ts index 49f0d5a1709..0c448ca907c 100644 --- a/apps/settings/src/app-types.ts +++ b/apps/settings/src/app-types.ts @@ -75,6 +75,7 @@ export interface IDeployDaemon { id: number, name: string, protocol: string, + exAppsCount: number, } export interface IExAppStatus { diff --git a/apps/settings/src/components/AppAPI/DaemonSelectionDialog.vue b/apps/settings/src/components/AppAPI/DaemonSelectionDialog.vue new file mode 100644 index 00000000000..696c77d19ce --- /dev/null +++ b/apps/settings/src/components/AppAPI/DaemonSelectionDialog.vue @@ -0,0 +1,41 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <NcDialog :open="show" + :name="t('settings', 'Choose Deploy Daemon for {appName}', {appName: app.name })" + size="normal" + @update:open="closeModal"> + <DaemonSelectionList :app="app" + :deploy-options="deployOptions" + @close="closeModal" /> + </NcDialog> +</template> + +<script setup> +import { defineProps, defineEmits } from 'vue' +import NcDialog from '@nextcloud/vue/components/NcDialog' +import DaemonSelectionList from './DaemonSelectionList.vue' + +defineProps({ + show: { + type: Boolean, + required: true, + }, + app: { + type: Object, + required: true, + }, + deployOptions: { + type: Object, + required: false, + default: () => ({}), + }, +}) + +const emit = defineEmits(['update:show']) +const closeModal = () => { + emit('update:show', false) +} +</script> diff --git a/apps/settings/src/components/AppAPI/DaemonSelectionEntry.vue b/apps/settings/src/components/AppAPI/DaemonSelectionEntry.vue new file mode 100644 index 00000000000..6b1cefde032 --- /dev/null +++ b/apps/settings/src/components/AppAPI/DaemonSelectionEntry.vue @@ -0,0 +1,77 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <NcListItem :name="itemTitle" + :details="isDefault ? t('settings', 'Default') : ''" + :force-display-actions="true" + :counter-number="daemon.exAppsCount" + :active="isDefault" + counter-type="highlighted" + @click.stop="selectDaemonAndInstall"> + <template #subname> + {{ daemon.accepts_deploy_id }} + </template> + </NcListItem> +</template> + +<script> +import NcListItem from '@nextcloud/vue/components/NcListItem' +import AppManagement from '../../mixins/AppManagement.js' +import { useAppsStore } from '../../store/apps-store' +import { useAppApiStore } from '../../store/app-api-store' + +export default { + name: 'DaemonSelectionEntry', + components: { + NcListItem, + }, + mixins: [AppManagement], // TODO: Convert to Composition API when AppManagement is refactored + props: { + daemon: { + type: Object, + required: true, + }, + isDefault: { + type: Boolean, + required: true, + }, + app: { + type: Object, + required: true, + }, + deployOptions: { + type: Object, + required: false, + default: () => ({}), + }, + }, + setup() { + const store = useAppsStore() + const appApiStore = useAppApiStore() + + return { + store, + appApiStore, + } + }, + computed: { + itemTitle() { + return this.daemon.name + ' - ' + this.daemon.display_name + }, + daemons() { + return this.appApiStore.dockerDaemons + }, + }, + methods: { + closeModal() { + this.$emit('close') + }, + selectDaemonAndInstall() { + this.closeModal() + this.enable(this.app.id, this.daemon, this.deployOptions) + }, + }, +} +</script> diff --git a/apps/settings/src/components/AppAPI/DaemonSelectionList.vue b/apps/settings/src/components/AppAPI/DaemonSelectionList.vue new file mode 100644 index 00000000000..701a17dbe24 --- /dev/null +++ b/apps/settings/src/components/AppAPI/DaemonSelectionList.vue @@ -0,0 +1,77 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <div class="daemon-selection-list"> + <ul v-if="dockerDaemons.length > 0" + :aria-label="t('settings', 'Registered Deploy daemons list')"> + <DaemonSelectionEntry v-for="daemon in dockerDaemons" + :key="daemon.id" + :daemon="daemon" + :is-default="defaultDaemon.name === daemon.name" + :app="app" + :deploy-options="deployOptions" + @close="closeModal" /> + </ul> + <NcEmptyContent v-else + class="daemon-selection-list__empty-content" + :name="t('settings', 'No Deploy daemons configured')" + :description="t('settings', 'Register a custom one or setup from available templates')"> + <template #icon> + <FormatListBullet :size="20" /> + </template> + <template #action> + <NcButton :href="appApiAdminPage"> + {{ t('settings', 'Manage Deploy daemons') }} + </NcButton> + </template> + </NcEmptyContent> + </div> +</template> + +<script setup> +import { computed, defineProps } from 'vue' +import { generateUrl } from '@nextcloud/router' + +import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' +import NcButton from '@nextcloud/vue/components/NcButton' +import FormatListBullet from 'vue-material-design-icons/FormatListBulleted.vue' +import DaemonSelectionEntry from './DaemonSelectionEntry.vue' +import { useAppApiStore } from '../../store/app-api-store.ts' + +defineProps({ + app: { + type: Object, + required: true, + }, + deployOptions: { + type: Object, + required: false, + default: () => ({}), + }, +}) + +const appApiStore = useAppApiStore() + +const dockerDaemons = computed(() => appApiStore.dockerDaemons) +const defaultDaemon = computed(() => appApiStore.defaultDaemon) +const appApiAdminPage = computed(() => generateUrl('/settings/admin/app_api')) +const emit = defineEmits(['close']) +const closeModal = () => { + emit('close') +} +</script> + +<style scoped lang="scss"> +.daemon-selection-list { + max-height: 350px; + overflow-y: scroll; + padding: 2rem; + + &__empty-content { + margin-top: 0; + text-align: center; + } +} +</style> diff --git a/apps/settings/src/components/AppList/AppItem.vue b/apps/settings/src/components/AppList/AppItem.vue index d0f39f3c74a..95a98a93cde 100644 --- a/apps/settings/src/components/AppList/AppItem.vue +++ b/apps/settings/src/components/AppList/AppItem.vue @@ -100,7 +100,7 @@ :aria-label="enableButtonTooltip" type="primary" :disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying" - @click.stop="enable(app.id)"> + @click.stop="enableButtonAction"> {{ enableButtonText }} </NcButton> <NcButton v-else-if="!app.active" @@ -111,6 +111,10 @@ @click.stop="forceEnable(app.id)"> {{ forceEnableButtonText }} </NcButton> + + <DaemonSelectionDialog v-if="app?.app_api && showSelectDaemonModal" + :show.sync="showSelectDaemonModal" + :app="app" /> </component> </component> </template> @@ -126,6 +130,7 @@ import NcButton from '@nextcloud/vue/components/NcButton' import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' import { mdiCogOutline } from '@mdi/js' import { useAppApiStore } from '../../store/app-api-store.ts' +import DaemonSelectionDialog from '../AppAPI/DaemonSelectionDialog.vue' export default { name: 'AppItem', @@ -134,6 +139,7 @@ export default { AppScore, NcButton, NcIconSvgWrapper, + DaemonSelectionDialog, }, mixins: [AppManagement, SvgFilterMixin], props: { @@ -177,6 +183,7 @@ export default { isSelected: false, scrolled: false, screenshotLoaded: false, + showSelectDaemonModal: false, } }, computed: { @@ -219,6 +226,23 @@ export default { getDataItemHeaders(columnName) { return this.useBundleView ? [this.headers, columnName].join(' ') : null }, + showSelectionModal() { + this.showSelectDaemonModal = true + }, + async enableButtonAction() { + if (!this.app?.app_api) { + this.enable(this.app.id) + return + } + await this.appApiStore.fetchDockerDaemons() + if (this.appApiStore.dockerDaemons.length === 1 && this.app.needsDownload) { + this.enable(this.app.id, this.appApiStore.dockerDaemons[0]) + } else if (this.app.needsDownload) { + this.showSelectionModal() + } else { + this.enable(this.app.id, this.app.daemon) + } + }, }, } </script> diff --git a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue index 67d4afa6566..0544c3848be 100644 --- a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue +++ b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue @@ -152,6 +152,7 @@ import { computed, ref } from 'vue' import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' +import { emit } from '@nextcloud/event-bus' import NcDialog from '@nextcloud/vue/components/NcDialog' import NcTextField from '@nextcloud/vue/components/NcTextField' @@ -277,8 +278,15 @@ export default { this.configuredDeployOptions = null }) }, - submitDeployOptions() { - this.enable(this.app.id, this.deployOptions) + async submitDeployOptions() { + await this.appApiStore.fetchDockerDaemons() + if (this.appApiStore.dockerDaemons.length === 1 && this.app.needsDownload) { + this.enable(this.app.id, this.appApiStore.dockerDaemons[0], this.deployOptions) + } else if (this.app.needsDownload) { + emit('showDaemonSelectionModal', this.deployOptions) + } else { + this.enable(this.app.id, this.app.daemon, this.deployOptions) + } this.$emit('update:show', false) }, }, diff --git a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue index 8a387b55ecf..eb66d8f3e3a 100644 --- a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue +++ b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue @@ -68,7 +68,7 @@ type="button" :value="enableButtonText" :disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying" - @click="enable(app.id)"> + @click="enableButtonAction"> <input v-else-if="!app.active && !app.canInstall" :title="forceEnableButtonTooltip" :aria-label="forceEnableButtonTooltip" @@ -195,11 +195,16 @@ <AppDeployOptionsModal v-if="app?.app_api" :show.sync="showDeployOptionsModal" :app="app" /> + <DaemonSelectionDialog v-if="app?.app_api" + :show.sync="showSelectDaemonModal" + :app="app" + :deploy-options="deployOptions" /> </div> </NcAppSidebarTab> </template> <script> +import { subscribe, unsubscribe } from '@nextcloud/event-bus' import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' import NcButton from '@nextcloud/vue/components/NcButton' import NcDateTime from '@nextcloud/vue/components/NcDateTime' @@ -207,6 +212,7 @@ import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' import NcSelect from '@nextcloud/vue/components/NcSelect' import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' import AppDeployOptionsModal from './AppDeployOptionsModal.vue' +import DaemonSelectionDialog from '../AppAPI/DaemonSelectionDialog.vue' import AppManagement from '../../mixins/AppManagement.js' import { mdiBugOutline, mdiFeatureSearchOutline, mdiStar, mdiTextBoxOutline, mdiTooltipQuestionOutline, mdiToyBrickPlusOutline } from '@mdi/js' @@ -224,6 +230,7 @@ export default { NcSelect, NcCheckboxRadioSwitch, AppDeployOptionsModal, + DaemonSelectionDialog, }, mixins: [AppManagement], @@ -256,6 +263,8 @@ export default { groupCheckedAppsData: false, removeData: false, showDeployOptionsModal: false, + showSelectDaemonModal: false, + deployOptions: null, } }, @@ -365,15 +374,40 @@ export default { this.removeData = false }, }, + beforeUnmount() { + this.deployOptions = null + unsubscribe('showDaemonSelectionModal') + }, mounted() { if (this.app.groups.length > 0) { this.groupCheckedAppsData = true } + subscribe('showDaemonSelectionModal', (deployOptions) => { + this.showSelectionModal(deployOptions) + }) }, methods: { toggleRemoveData() { this.removeData = !this.removeData }, + showSelectionModal(deployOptions = null) { + this.deployOptions = deployOptions + this.showSelectDaemonModal = true + }, + async enableButtonAction() { + if (!this.app?.app_api) { + this.enable(this.app.id) + return + } + await this.appApiStore.fetchDockerDaemons() + if (this.appApiStore.dockerDaemons.length === 1 && this.app.needsDownload) { + this.enable(this.app.id, this.appApiStore.dockerDaemons[0]) + } else if (this.app.needsDownload) { + this.showSelectionModal() + } else { + this.enable(this.app.id, this.app.daemon) + } + }, }, } </script> diff --git a/apps/settings/src/mixins/AppManagement.js b/apps/settings/src/mixins/AppManagement.js index b877b8dd88e..3822658589d 100644 --- a/apps/settings/src/mixins/AppManagement.js +++ b/apps/settings/src/mixins/AppManagement.js @@ -188,9 +188,9 @@ export default { .catch((error) => { showError(error) }) } }, - enable(appId, deployOptions = []) { + enable(appId, daemon = null, deployOptions = {}) { if (this.app?.app_api) { - this.appApiStore.enableApp(appId, deployOptions) + this.appApiStore.enableApp(appId, daemon, deployOptions) .then(() => { rebuildNavigation() }) .catch((error) => { showError(error) }) } else { diff --git a/apps/settings/src/store/app-api-store.ts b/apps/settings/src/store/app-api-store.ts index f2f950d6948..769f212ebd7 100644 --- a/apps/settings/src/store/app-api-store.ts +++ b/apps/settings/src/store/app-api-store.ts @@ -25,6 +25,7 @@ interface AppApiState { statusUpdater: number | null | undefined daemonAccessible: boolean defaultDaemon: IDeployDaemon | null + dockerDaemons: IDeployDaemon[] } export const useAppApiStore = defineStore('app-api-apps', { @@ -36,6 +37,7 @@ export const useAppApiStore = defineStore('app-api-apps', { statusUpdater: null, daemonAccessible: loadState('settings', 'defaultDaemonConfigAccessible', false), defaultDaemon: loadState('settings', 'defaultDaemonConfig', null), + dockerDaemons: [], }), getters: { @@ -76,12 +78,12 @@ export const useAppApiStore = defineStore('app-api-apps', { }) }, - enableApp(appId: string, deployOptions: IDeployOptions[] = []) { + enableApp(appId: string, daemon: IDeployDaemon, deployOptions: IDeployOptions) { this.setLoading(appId, true) this.setLoading('install', true) return confirmPassword().then(() => { - return axios.post(generateUrl(`/apps/app_api/apps/enable/${appId}`), { deployOptions }) + return axios.post(generateUrl(`/apps/app_api/apps/enable/${appId}/${daemon.name}`), { deployOptions }) .then((response) => { this.setLoading(appId, false) this.setLoading('install', false) @@ -91,7 +93,7 @@ export const useAppApiStore = defineStore('app-api-apps', { if (!app.installed) { app.installed = true app.needsDownload = false - app.daemon = this.defaultDaemon + app.daemon = daemon app.status = { type: 'install', action: 'deploy', @@ -293,6 +295,18 @@ export const useAppApiStore = defineStore('app-api-apps', { }) }, + async fetchDockerDaemons() { + try { + const { data } = await axios.get(generateUrl('/apps/app_api/daemons')) + this.defaultDaemon = data.daemons.find((daemon: IDeployDaemon) => daemon.name === data.default_daemon_config) + this.dockerDaemons = data.daemons.filter((daemon: IDeployDaemon) => daemon.accepts_deploy_id === 'docker-install') + } catch (error) { + logger.error('[app-api-store] Failed to fetch Docker daemons', { error }) + return false + } + return true + }, + updateAppsStatus() { clearInterval(this.statusUpdater as number) const initializingOrDeployingApps = this.getInitializingOrDeployingApps diff --git a/apps/settings/tests/Settings/Admin/SharingTest.php b/apps/settings/tests/Settings/Admin/SharingTest.php index 12ab5c3cada..f37ade2171f 100644 --- a/apps/settings/tests/Settings/Admin/SharingTest.php +++ b/apps/settings/tests/Settings/Admin/SharingTest.php @@ -57,7 +57,8 @@ class SharingTest extends TestCase { $this->appConfig ->method('getValueBool') ->willReturnMap([ - ['core', 'shareapi_allow_federation_on_public_shares', false, false, true], + ['core', 'shareapi_allow_federation_on_public_shares', true], + ['core', 'shareapi_enable_link_password_by_default', true], ]); $this->config @@ -82,7 +83,6 @@ class SharingTest extends TestCase { ['core', 'shareapi_enforce_expire_date', 'no', 'no'], ['core', 'shareapi_exclude_groups', 'no', 'no'], ['core', 'shareapi_public_link_disclaimertext', '', 'Lorem ipsum'], - ['core', 'shareapi_enable_link_password_by_default', 'no', 'yes'], ['core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL, Constants::PERMISSION_ALL], ['core', 'shareapi_default_internal_expire_date', 'no', 'no'], ['core', 'shareapi_internal_expire_after_n_days', '7', '7'], diff --git a/apps/systemtags/l10n/pt_BR.js b/apps/systemtags/l10n/pt_BR.js index 6f931085ed1..4b8ad122508 100644 --- a/apps/systemtags/l10n/pt_BR.js +++ b/apps/systemtags/l10n/pt_BR.js @@ -80,6 +80,7 @@ OC.L10N.register( "Search tag" : "Pesquisar etiqueta", "Change tag color" : "Alterar cor da etiqueta", "Create new tag" : "Criar etiqueta nova", + "Choose tags for the selected files" : "Escolha tags para os arquivos selecionados", "Cancel" : "Cancelar", "Apply" : "Aplicar", "Failed to load tags" : "Erro ao carregar etiquetas", diff --git a/apps/systemtags/l10n/pt_BR.json b/apps/systemtags/l10n/pt_BR.json index c5bc3eeedd5..a2a6ae74c6a 100644 --- a/apps/systemtags/l10n/pt_BR.json +++ b/apps/systemtags/l10n/pt_BR.json @@ -78,6 +78,7 @@ "Search tag" : "Pesquisar etiqueta", "Change tag color" : "Alterar cor da etiqueta", "Create new tag" : "Criar etiqueta nova", + "Choose tags for the selected files" : "Escolha tags para os arquivos selecionados", "Cancel" : "Cancelar", "Apply" : "Aplicar", "Failed to load tags" : "Erro ao carregar etiquetas", diff --git a/apps/theming/css/default.css b/apps/theming/css/default.css index 6a6628f074a..41f0272f017 100644 --- a/apps/theming/css/default.css +++ b/apps/theming/css/default.css @@ -79,9 +79,9 @@ --header-menu-item-height: 44px; /* An alpha mask to be applied to all icons on the navigation bar (header menu). * Icons are have a size of 20px but usually we use MDI which have a content of 16px so 2px padding top bottom, - * for better gradient we set those 2px (10% of height) as start and stop positions, this is also somewhat size agnostic as we only depend on the percentage. + * for better gradient we must at first begin at those 2px (10% of height) as start and stop positions. */ - --header-menu-icon-mask: linear-gradient(var(--color-background-plain-text) 10%, color-mix(in srgb, var(--color-background-plain-text), 25% transparent) 90%) alpha; + --header-menu-icon-mask: linear-gradient(var(--color-background-plain-text) 25%, color-mix(in srgb, var(--color-background-plain-text), 55% transparent) 90%) alpha; --navigation-width: 300px; --sidebar-min-width: 300px; diff --git a/apps/theming/lib/Themes/DefaultTheme.php b/apps/theming/lib/Themes/DefaultTheme.php index 7d06926c52f..bdd3048a498 100644 --- a/apps/theming/lib/Themes/DefaultTheme.php +++ b/apps/theming/lib/Themes/DefaultTheme.php @@ -196,9 +196,9 @@ class DefaultTheme implements ITheme { '--header-menu-item-height' => '44px', /* An alpha mask to be applied to all icons on the navigation bar (header menu). * Icons are have a size of 20px but usually we use MDI which have a content of 16px so 2px padding top bottom, - * for better gradient we set those 2px (10% of height) as start and stop positions, this is also somewhat size agnostic as we only depend on the percentage. + * for better gradient we must at first begin at those 2px (10% of height) as start and stop positions. */ - '--header-menu-icon-mask' => 'linear-gradient(var(--color-background-plain-text) 10%, color-mix(in srgb, var(--color-background-plain-text), 25% transparent) 90%) alpha', + '--header-menu-icon-mask' => 'linear-gradient(var(--color-background-plain-text) 25%, color-mix(in srgb, var(--color-background-plain-text), 55% transparent) 90%) alpha', // various structure data '--navigation-width' => '300px', diff --git a/apps/workflowengine/l10n/es.js b/apps/workflowengine/l10n/es.js index ffc254ad2b0..4dec679ca90 100644 --- a/apps/workflowengine/l10n/es.js +++ b/apps/workflowengine/l10n/es.js @@ -70,7 +70,7 @@ OC.L10N.register( "Select groups" : "Seleccionar grupos", "Groups" : "Grupos", "Type to search for group …" : "Teclee para buscar un grupo …", - "Select a trigger" : "Seleccione un trigger", + "Select a trigger" : "Seleccione un disparador", "At least one event must be selected" : "Has de seleccionar al menos un evento", "Add new flow" : "Añadir nuevo flujo", "The configuration is invalid" : "La configuración es incorrecta", diff --git a/apps/workflowengine/l10n/es.json b/apps/workflowengine/l10n/es.json index 2c00e75238c..f25a5a8014b 100644 --- a/apps/workflowengine/l10n/es.json +++ b/apps/workflowengine/l10n/es.json @@ -68,7 +68,7 @@ "Select groups" : "Seleccionar grupos", "Groups" : "Grupos", "Type to search for group …" : "Teclee para buscar un grupo …", - "Select a trigger" : "Seleccione un trigger", + "Select a trigger" : "Seleccione un disparador", "At least one event must be selected" : "Has de seleccionar al menos un evento", "Add new flow" : "Añadir nuevo flujo", "The configuration is invalid" : "La configuración es incorrecta", |