From e8924c9ab7faad8e224ccc96e7c84ba12928630e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 07:56:19 +0200 Subject: [PATCH 01/21] feat(containers): sort the tiles in a container by hand, alphabetically, by most or last used, or at random Most and last used read counts kept in the viewer's own browser, never sent to the server; a viewer can forget them. The order is applied when the page is viewed and never written back, and random holds for the page load. REQ-TSO-001..004, REQ-CONT-007 (launcher-tile-sorting) --- l10n/be.js | 13 +- l10n/be.json | 13 +- l10n/bg.js | 13 +- l10n/bg.json | 13 +- l10n/bs.js | 13 +- l10n/bs.json | 13 +- l10n/ca.js | 13 +- l10n/ca.json | 13 +- l10n/cs.js | 13 +- l10n/cs.json | 13 +- l10n/da.js | 13 +- l10n/da.json | 13 +- l10n/de.js | 13 +- l10n/de.json | 13 +- l10n/el.js | 13 +- l10n/el.json | 13 +- l10n/en.js | 13 +- l10n/en.json | 13 +- l10n/es.js | 13 +- l10n/es.json | 13 +- l10n/et.js | 13 +- l10n/et.json | 13 +- l10n/fi.js | 13 +- l10n/fi.json | 13 +- l10n/fr.js | 13 +- l10n/fr.json | 13 +- l10n/ga.js | 13 +- l10n/ga.json | 13 +- l10n/hr.js | 13 +- l10n/hr.json | 13 +- l10n/hu.js | 13 +- l10n/hu.json | 13 +- l10n/is.js | 13 +- l10n/is.json | 13 +- l10n/it.js | 13 +- l10n/it.json | 13 +- l10n/lb.js | 13 +- l10n/lb.json | 13 +- l10n/lt.js | 13 +- l10n/lt.json | 13 +- l10n/lv.js | 13 +- l10n/lv.json | 13 +- l10n/mk.js | 13 +- l10n/mk.json | 13 +- l10n/mt.js | 13 +- l10n/mt.json | 13 +- l10n/nb.js | 13 +- l10n/nb.json | 13 +- l10n/nl.js | 13 +- l10n/nl.json | 13 +- l10n/pl.js | 13 +- l10n/pl.json | 13 +- l10n/pt.js | 13 +- l10n/pt.json | 13 +- l10n/rm.js | 13 +- l10n/rm.json | 13 +- l10n/ro.js | 13 +- l10n/ro.json | 13 +- l10n/ru.js | 13 +- l10n/ru.json | 13 +- l10n/sk.js | 13 +- l10n/sk.json | 13 +- l10n/sl.js | 13 +- l10n/sl.json | 13 +- l10n/sq.js | 13 +- l10n/sq.json | 13 +- l10n/sr.js | 13 +- l10n/sr.json | 13 +- l10n/sv.js | 13 +- l10n/sv.json | 13 +- l10n/tr.js | 13 +- l10n/tr.json | 13 +- l10n/uk.js | 13 +- l10n/uk.json | 13 +- .../Widgets/Renderers/ContainerWidget.vue | 177 +++++++++++++- .../Widgets/Renderers/ContainerWidgetForm.vue | 139 +++++++++++ .../__tests__/ContainerWidget.sort.spec.js | 226 ++++++++++++++++++ .../__tests__/ContainerWidgetForm.spec.js | 110 +++++++++ src/composables/useTileClickTracking.js | 131 ++++++++++ src/constants/widgetRegistry.js | 3 + src/utils/sortContainerTiles.js | 116 +++++++++ 81 files changed, 1786 insertions(+), 78 deletions(-) create mode 100644 src/components/Widgets/Renderers/ContainerWidgetForm.vue create mode 100644 src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js create mode 100644 src/components/Widgets/Renderers/__tests__/ContainerWidgetForm.spec.js create mode 100644 src/utils/sortContainerTiles.js diff --git a/l10n/be.js b/l10n/be.js index 4fc3bafb1..009cd5a99 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -1312,6 +1312,17 @@ OC.L10N.register( "Profile fields saved." : "Палі профілю захаваны.", "Save profile fields" : "Захаваць палі профілю", "The profile fields could not be loaded." : "Не ўдалося загрузіць палі профілю.", - "The profile fields could not be saved." : "Не ўдалося захаваць палі профілю." + "The profile fields could not be saved." : "Не ўдалося захаваць палі профілю.", + "Forget my usage" : "Забыць маё выкарыстанне", + "Sorted alphabetically" : "Упарадкавана па алфавіце", + "Sorted by most used" : "Упарадкавана па частаце выкарыстання", + "Sorted by last used" : "Упарадкавана па апошнім выкарыстанні", + "Sorted at random" : "Упарадкавана выпадкова", + "Sort tiles" : "Парадак плітак", + "By hand" : "Уручную", + "Alphabetically" : "Па алфавіце", + "Most used" : "Найчасцей выкарыстоўваныя", + "Last used" : "Апошнія выкарыстаныя", + "At random" : "Выпадкова" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/be.json b/l10n/be.json index f1b3999ce..4b7180f08 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Палі профілю захаваны.", "Save profile fields": "Захаваць палі профілю", "The profile fields could not be loaded.": "Не ўдалося загрузіць палі профілю.", - "The profile fields could not be saved.": "Не ўдалося захаваць палі профілю." + "The profile fields could not be saved.": "Не ўдалося захаваць палі профілю.", + "Forget my usage": "Забыць маё выкарыстанне", + "Sorted alphabetically": "Упарадкавана па алфавіце", + "Sorted by most used": "Упарадкавана па частаце выкарыстання", + "Sorted by last used": "Упарадкавана па апошнім выкарыстанні", + "Sorted at random": "Упарадкавана выпадкова", + "Sort tiles": "Парадак плітак", + "By hand": "Уручную", + "Alphabetically": "Па алфавіце", + "Most used": "Найчасцей выкарыстоўваныя", + "Last used": "Апошнія выкарыстаныя", + "At random": "Выпадкова" } } diff --git a/l10n/bg.js b/l10n/bg.js index 94a897089..29818f4a3 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Полетата на профила са запазени.", "Save profile fields" : "Запазване на полетата на профила", "The profile fields could not be loaded." : "Полетата на профила не можаха да се заредят.", - "The profile fields could not be saved." : "Полетата на профила не можаха да се запазят." + "The profile fields could not be saved." : "Полетата на профила не можаха да се запазят.", + "Forget my usage" : "Забрави използването ми", + "Sorted alphabetically" : "Подредено по азбучен ред", + "Sorted by most used" : "Подредено по най-използвани", + "Sorted by last used" : "Подредено по последно използвани", + "Sorted at random" : "Подредено в случаен ред", + "Sort tiles" : "Подреждане на плочките", + "By hand" : "Ръчно", + "Alphabetically" : "По азбучен ред", + "Most used" : "Най-използвани", + "Last used" : "Последно използвани", + "At random" : "В случаен ред" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/bg.json b/l10n/bg.json index 8f5c8ea7b..0879485b6 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Полетата на профила са запазени.", "Save profile fields": "Запазване на полетата на профила", "The profile fields could not be loaded.": "Полетата на профила не можаха да се заредят.", - "The profile fields could not be saved.": "Полетата на профила не можаха да се запазят." + "The profile fields could not be saved.": "Полетата на профила не можаха да се запазят.", + "Forget my usage": "Забрави използването ми", + "Sorted alphabetically": "Подредено по азбучен ред", + "Sorted by most used": "Подредено по най-използвани", + "Sorted by last used": "Подредено по последно използвани", + "Sorted at random": "Подредено в случаен ред", + "Sort tiles": "Подреждане на плочките", + "By hand": "Ръчно", + "Alphabetically": "По азбучен ред", + "Most used": "Най-използвани", + "Last used": "Последно използвани", + "At random": "В случаен ред" } } diff --git a/l10n/bs.js b/l10n/bs.js index 361af2eed..1f9c11827 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Polja profila su spremljena.", "Save profile fields" : "Spremi polja profila", "The profile fields could not be loaded." : "Polja profila nisu mogla biti učitana.", - "The profile fields could not be saved." : "Polja profila nisu mogla biti spremljena." + "The profile fields could not be saved." : "Polja profila nisu mogla biti spremljena.", + "Forget my usage" : "Zaboravi moje korištenje", + "Sorted alphabetically" : "Poredano abecedno", + "Sorted by most used" : "Poredano po najčešće korištenim", + "Sorted by last used" : "Poredano po zadnje korištenim", + "Sorted at random" : "Poredano nasumično", + "Sort tiles" : "Poredaj pločice", + "By hand" : "Ručno", + "Alphabetically" : "Abecedno", + "Most used" : "Najčešće korišteno", + "Last used" : "Zadnje korišteno", + "At random" : "Nasumično" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/bs.json b/l10n/bs.json index 3afcedf8a..4c194ed67 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Polja profila su spremljena.", "Save profile fields": "Spremi polja profila", "The profile fields could not be loaded.": "Polja profila nisu mogla biti učitana.", - "The profile fields could not be saved.": "Polja profila nisu mogla biti spremljena." + "The profile fields could not be saved.": "Polja profila nisu mogla biti spremljena.", + "Forget my usage": "Zaboravi moje korištenje", + "Sorted alphabetically": "Poredano abecedno", + "Sorted by most used": "Poredano po najčešće korištenim", + "Sorted by last used": "Poredano po zadnje korištenim", + "Sorted at random": "Poredano nasumično", + "Sort tiles": "Poredaj pločice", + "By hand": "Ručno", + "Alphabetically": "Abecedno", + "Most used": "Najčešće korišteno", + "Last used": "Zadnje korišteno", + "At random": "Nasumično" } } diff --git a/l10n/ca.js b/l10n/ca.js index bf45de116..86332cdff 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "S'han desat els camps del perfil.", "Save profile fields" : "Desa els camps del perfil", "The profile fields could not be loaded." : "No s'han pogut carregar els camps del perfil.", - "The profile fields could not be saved." : "No s'han pogut desar els camps del perfil." + "The profile fields could not be saved." : "No s'han pogut desar els camps del perfil.", + "Forget my usage" : "Oblida el meu ús", + "Sorted alphabetically" : "Ordenat alfabèticament", + "Sorted by most used" : "Ordenat pels més usats", + "Sorted by last used" : "Ordenat pels darrers usats", + "Sorted at random" : "Ordenat a l'atzar", + "Sort tiles" : "Ordena les rajoles", + "By hand" : "A mà", + "Alphabetically" : "Alfabèticament", + "Most used" : "Els més usats", + "Last used" : "Darrers usats", + "At random" : "A l'atzar" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/ca.json b/l10n/ca.json index ed8885f1a..8b4868e41 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "S'han desat els camps del perfil.", "Save profile fields": "Desa els camps del perfil", "The profile fields could not be loaded.": "No s'han pogut carregar els camps del perfil.", - "The profile fields could not be saved.": "No s'han pogut desar els camps del perfil." + "The profile fields could not be saved.": "No s'han pogut desar els camps del perfil.", + "Forget my usage": "Oblida el meu ús", + "Sorted alphabetically": "Ordenat alfabèticament", + "Sorted by most used": "Ordenat pels més usats", + "Sorted by last used": "Ordenat pels darrers usats", + "Sorted at random": "Ordenat a l'atzar", + "Sort tiles": "Ordena les rajoles", + "By hand": "A mà", + "Alphabetically": "Alfabèticament", + "Most used": "Els més usats", + "Last used": "Darrers usats", + "At random": "A l'atzar" } } diff --git a/l10n/cs.js b/l10n/cs.js index d11ca0b25..e5c89f275 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Pole profilu jsou uložena.", "Save profile fields" : "Uložit pole profilu", "The profile fields could not be loaded." : "Pole profilu se nepodařilo načíst.", - "The profile fields could not be saved." : "Pole profilu se nepodařilo uložit." + "The profile fields could not be saved." : "Pole profilu se nepodařilo uložit.", + "Forget my usage" : "Zapomenout mé používání", + "Sorted alphabetically" : "Seřazeno abecedně", + "Sorted by most used" : "Seřazeno podle nejpoužívanějších", + "Sorted by last used" : "Seřazeno podle naposledy použitých", + "Sorted at random" : "Seřazeno náhodně", + "Sort tiles" : "Řazení dlaždic", + "By hand" : "Ručně", + "Alphabetically" : "Abecedně", + "Most used" : "Nejpoužívanější", + "Last used" : "Naposledy použité", + "At random" : "Náhodně" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/cs.json b/l10n/cs.json index 3f64c588c..6b523cb30 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Pole profilu jsou uložena.", "Save profile fields": "Uložit pole profilu", "The profile fields could not be loaded.": "Pole profilu se nepodařilo načíst.", - "The profile fields could not be saved.": "Pole profilu se nepodařilo uložit." + "The profile fields could not be saved.": "Pole profilu se nepodařilo uložit.", + "Forget my usage": "Zapomenout mé používání", + "Sorted alphabetically": "Seřazeno abecedně", + "Sorted by most used": "Seřazeno podle nejpoužívanějších", + "Sorted by last used": "Seřazeno podle naposledy použitých", + "Sorted at random": "Seřazeno náhodně", + "Sort tiles": "Řazení dlaždic", + "By hand": "Ručně", + "Alphabetically": "Abecedně", + "Most used": "Nejpoužívanější", + "Last used": "Naposledy použité", + "At random": "Náhodně" } } diff --git a/l10n/da.js b/l10n/da.js index ff77c1aaa..dde69e192 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilfelterne er gemt.", "Save profile fields" : "Gem profilfelter", "The profile fields could not be loaded." : "Profilfelterne kunne ikke indlæses.", - "The profile fields could not be saved." : "Profilfelterne kunne ikke gemmes." + "The profile fields could not be saved." : "Profilfelterne kunne ikke gemmes.", + "Forget my usage" : "Glem min brug", + "Sorted alphabetically" : "Sorteret alfabetisk", + "Sorted by most used" : "Sorteret efter mest brugte", + "Sorted by last used" : "Sorteret efter senest brugte", + "Sorted at random" : "Sorteret tilfældigt", + "Sort tiles" : "Sortér felter", + "By hand" : "Manuelt", + "Alphabetically" : "Alfabetisk", + "Most used" : "Mest brugte", + "Last used" : "Senest brugte", + "At random" : "Tilfældigt" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/da.json b/l10n/da.json index 78fd8908a..840976e06 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilfelterne er gemt.", "Save profile fields": "Gem profilfelter", "The profile fields could not be loaded.": "Profilfelterne kunne ikke indlæses.", - "The profile fields could not be saved.": "Profilfelterne kunne ikke gemmes." + "The profile fields could not be saved.": "Profilfelterne kunne ikke gemmes.", + "Forget my usage": "Glem min brug", + "Sorted alphabetically": "Sorteret alfabetisk", + "Sorted by most used": "Sorteret efter mest brugte", + "Sorted by last used": "Sorteret efter senest brugte", + "Sorted at random": "Sorteret tilfældigt", + "Sort tiles": "Sortér felter", + "By hand": "Manuelt", + "Alphabetically": "Alfabetisk", + "Most used": "Mest brugte", + "Last used": "Senest brugte", + "At random": "Tilfældigt" } } diff --git a/l10n/de.js b/l10n/de.js index 6063757b5..9e008de03 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilfelder gespeichert.", "Save profile fields" : "Profilfelder speichern", "The profile fields could not be loaded." : "Die Profilfelder konnten nicht geladen werden.", - "The profile fields could not be saved." : "Die Profilfelder konnten nicht gespeichert werden." + "The profile fields could not be saved." : "Die Profilfelder konnten nicht gespeichert werden.", + "Forget my usage" : "Meine Nutzung vergessen", + "Sorted alphabetically" : "Alphabetisch sortiert", + "Sorted by most used" : "Nach häufigster Nutzung sortiert", + "Sorted by last used" : "Nach letzter Nutzung sortiert", + "Sorted at random" : "Zufällig sortiert", + "Sort tiles" : "Kacheln sortieren", + "By hand" : "Von Hand", + "Alphabetically" : "Alphabetisch", + "Most used" : "Am häufigsten genutzt", + "Last used" : "Zuletzt genutzt", + "At random" : "Zufällig" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/de.json b/l10n/de.json index 5468a77de..bf84b97e3 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilfelder gespeichert.", "Save profile fields": "Profilfelder speichern", "The profile fields could not be loaded.": "Die Profilfelder konnten nicht geladen werden.", - "The profile fields could not be saved.": "Die Profilfelder konnten nicht gespeichert werden." + "The profile fields could not be saved.": "Die Profilfelder konnten nicht gespeichert werden.", + "Forget my usage": "Meine Nutzung vergessen", + "Sorted alphabetically": "Alphabetisch sortiert", + "Sorted by most used": "Nach häufigster Nutzung sortiert", + "Sorted by last used": "Nach letzter Nutzung sortiert", + "Sorted at random": "Zufällig sortiert", + "Sort tiles": "Kacheln sortieren", + "By hand": "Von Hand", + "Alphabetically": "Alphabetisch", + "Most used": "Am häufigsten genutzt", + "Last used": "Zuletzt genutzt", + "At random": "Zufällig" } } diff --git a/l10n/el.js b/l10n/el.js index 4fe15f6bc..15a512709 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Τα πεδία προφίλ αποθηκεύτηκαν.", "Save profile fields" : "Αποθήκευση πεδίων προφίλ", "The profile fields could not be loaded." : "Δεν ήταν δυνατή η φόρτωση των πεδίων προφίλ.", - "The profile fields could not be saved." : "Δεν ήταν δυνατή η αποθήκευση των πεδίων προφίλ." + "The profile fields could not be saved." : "Δεν ήταν δυνατή η αποθήκευση των πεδίων προφίλ.", + "Forget my usage" : "Διαγραφή της χρήσης μου", + "Sorted alphabetically" : "Ταξινομημένα αλφαβητικά", + "Sorted by most used" : "Ταξινομημένα κατά συχνότερη χρήση", + "Sorted by last used" : "Ταξινομημένα κατά τελευταία χρήση", + "Sorted at random" : "Ταξινομημένα τυχαία", + "Sort tiles" : "Ταξινόμηση πλακιδίων", + "By hand" : "Χειροκίνητα", + "Alphabetically" : "Αλφαβητικά", + "Most used" : "Συχνότερα χρησιμοποιούμενα", + "Last used" : "Τελευταία χρήση", + "At random" : "Τυχαία" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/el.json b/l10n/el.json index c4aaf0219..5c8950f27 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Τα πεδία προφίλ αποθηκεύτηκαν.", "Save profile fields": "Αποθήκευση πεδίων προφίλ", "The profile fields could not be loaded.": "Δεν ήταν δυνατή η φόρτωση των πεδίων προφίλ.", - "The profile fields could not be saved.": "Δεν ήταν δυνατή η αποθήκευση των πεδίων προφίλ." + "The profile fields could not be saved.": "Δεν ήταν δυνατή η αποθήκευση των πεδίων προφίλ.", + "Forget my usage": "Διαγραφή της χρήσης μου", + "Sorted alphabetically": "Ταξινομημένα αλφαβητικά", + "Sorted by most used": "Ταξινομημένα κατά συχνότερη χρήση", + "Sorted by last used": "Ταξινομημένα κατά τελευταία χρήση", + "Sorted at random": "Ταξινομημένα τυχαία", + "Sort tiles": "Ταξινόμηση πλακιδίων", + "By hand": "Χειροκίνητα", + "Alphabetically": "Αλφαβητικά", + "Most used": "Συχνότερα χρησιμοποιούμενα", + "Last used": "Τελευταία χρήση", + "At random": "Τυχαία" } } diff --git a/l10n/en.js b/l10n/en.js index c86a4c71c..b2c2ba066 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -1301,7 +1301,18 @@ OC.L10N.register( "Profile fields saved." : "Profile fields saved.", "Save profile fields" : "Save profile fields", "The profile fields could not be loaded." : "The profile fields could not be loaded.", - "The profile fields could not be saved." : "The profile fields could not be saved." + "The profile fields could not be saved." : "The profile fields could not be saved.", + "Forget my usage" : "Forget my usage", + "Sorted alphabetically" : "Sorted alphabetically", + "Sorted by most used" : "Sorted by most used", + "Sorted by last used" : "Sorted by last used", + "Sorted at random" : "Sorted at random", + "Sort tiles" : "Sort tiles", + "By hand" : "By hand", + "Alphabetically" : "Alphabetically", + "Most used" : "Most used", + "Last used" : "Last used", + "At random" : "At random" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/en.json b/l10n/en.json index 50a16c214..28106e6b2 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -1266,6 +1266,17 @@ "Profile fields saved.": "Profile fields saved.", "Save profile fields": "Save profile fields", "The profile fields could not be loaded.": "The profile fields could not be loaded.", - "The profile fields could not be saved.": "The profile fields could not be saved." + "The profile fields could not be saved.": "The profile fields could not be saved.", + "Forget my usage": "Forget my usage", + "Sorted alphabetically": "Sorted alphabetically", + "Sorted by most used": "Sorted by most used", + "Sorted by last used": "Sorted by last used", + "Sorted at random": "Sorted at random", + "Sort tiles": "Sort tiles", + "By hand": "By hand", + "Alphabetically": "Alphabetically", + "Most used": "Most used", + "Last used": "Last used", + "At random": "At random" } } diff --git a/l10n/es.js b/l10n/es.js index 3c4155627..75312eba8 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Campos del perfil guardados.", "Save profile fields" : "Guardar campos del perfil", "The profile fields could not be loaded." : "No se pudieron cargar los campos del perfil.", - "The profile fields could not be saved." : "No se pudieron guardar los campos del perfil." + "The profile fields could not be saved." : "No se pudieron guardar los campos del perfil.", + "Forget my usage" : "Olvidar mi uso", + "Sorted alphabetically" : "Ordenado alfabéticamente", + "Sorted by most used" : "Ordenado por más usados", + "Sorted by last used" : "Ordenado por último uso", + "Sorted at random" : "Ordenado al azar", + "Sort tiles" : "Ordenar mosaicos", + "By hand" : "A mano", + "Alphabetically" : "Alfabéticamente", + "Most used" : "Más usados", + "Last used" : "Último uso", + "At random" : "Al azar" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/es.json b/l10n/es.json index b5bb51d96..c63e2e707 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Campos del perfil guardados.", "Save profile fields": "Guardar campos del perfil", "The profile fields could not be loaded.": "No se pudieron cargar los campos del perfil.", - "The profile fields could not be saved.": "No se pudieron guardar los campos del perfil." + "The profile fields could not be saved.": "No se pudieron guardar los campos del perfil.", + "Forget my usage": "Olvidar mi uso", + "Sorted alphabetically": "Ordenado alfabéticamente", + "Sorted by most used": "Ordenado por más usados", + "Sorted by last used": "Ordenado por último uso", + "Sorted at random": "Ordenado al azar", + "Sort tiles": "Ordenar mosaicos", + "By hand": "A mano", + "Alphabetically": "Alfabéticamente", + "Most used": "Más usados", + "Last used": "Último uso", + "At random": "Al azar" } } diff --git a/l10n/et.js b/l10n/et.js index 176dbb655..02d6525db 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profiili väljad salvestatud.", "Save profile fields" : "Salvesta profiili väljad", "The profile fields could not be loaded." : "Profiili välju ei õnnestunud laadida.", - "The profile fields could not be saved." : "Profiili välju ei õnnestunud salvestada." + "The profile fields could not be saved." : "Profiili välju ei õnnestunud salvestada.", + "Forget my usage" : "Unusta minu kasutus", + "Sorted alphabetically" : "Sorditud tähestiku järgi", + "Sorted by most used" : "Sorditud enim kasutatu järgi", + "Sorted by last used" : "Sorditud viimati kasutatu järgi", + "Sorted at random" : "Sorditud juhuslikult", + "Sort tiles" : "Paanide järjestus", + "By hand" : "Käsitsi", + "Alphabetically" : "Tähestiku järgi", + "Most used" : "Enim kasutatud", + "Last used" : "Viimati kasutatud", + "At random" : "Juhuslikult" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/et.json b/l10n/et.json index 856cf2a6a..d6760c3fc 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profiili väljad salvestatud.", "Save profile fields": "Salvesta profiili väljad", "The profile fields could not be loaded.": "Profiili välju ei õnnestunud laadida.", - "The profile fields could not be saved.": "Profiili välju ei õnnestunud salvestada." + "The profile fields could not be saved.": "Profiili välju ei õnnestunud salvestada.", + "Forget my usage": "Unusta minu kasutus", + "Sorted alphabetically": "Sorditud tähestiku järgi", + "Sorted by most used": "Sorditud enim kasutatu järgi", + "Sorted by last used": "Sorditud viimati kasutatu järgi", + "Sorted at random": "Sorditud juhuslikult", + "Sort tiles": "Paanide järjestus", + "By hand": "Käsitsi", + "Alphabetically": "Tähestiku järgi", + "Most used": "Enim kasutatud", + "Last used": "Viimati kasutatud", + "At random": "Juhuslikult" } } diff --git a/l10n/fi.js b/l10n/fi.js index cddec8a91..a0949456f 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profiilikentät tallennettu.", "Save profile fields" : "Tallenna profiilikentät", "The profile fields could not be loaded." : "Profiilikenttiä ei voitu ladata.", - "The profile fields could not be saved." : "Profiilikenttiä ei voitu tallentaa." + "The profile fields could not be saved." : "Profiilikenttiä ei voitu tallentaa.", + "Forget my usage" : "Unohda käyttöni", + "Sorted alphabetically" : "Järjestetty aakkosjärjestykseen", + "Sorted by most used" : "Järjestetty eniten käytetyn mukaan", + "Sorted by last used" : "Järjestetty viimeksi käytetyn mukaan", + "Sorted at random" : "Järjestetty satunnaisesti", + "Sort tiles" : "Ruutujen järjestys", + "By hand" : "Käsin", + "Alphabetically" : "Aakkosjärjestyksessä", + "Most used" : "Eniten käytetyt", + "Last used" : "Viimeksi käytetyt", + "At random" : "Satunnaisesti" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/fi.json b/l10n/fi.json index 943c32a29..4cd802cbf 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profiilikentät tallennettu.", "Save profile fields": "Tallenna profiilikentät", "The profile fields could not be loaded.": "Profiilikenttiä ei voitu ladata.", - "The profile fields could not be saved.": "Profiilikenttiä ei voitu tallentaa." + "The profile fields could not be saved.": "Profiilikenttiä ei voitu tallentaa.", + "Forget my usage": "Unohda käyttöni", + "Sorted alphabetically": "Järjestetty aakkosjärjestykseen", + "Sorted by most used": "Järjestetty eniten käytetyn mukaan", + "Sorted by last used": "Järjestetty viimeksi käytetyn mukaan", + "Sorted at random": "Järjestetty satunnaisesti", + "Sort tiles": "Ruutujen järjestys", + "By hand": "Käsin", + "Alphabetically": "Aakkosjärjestyksessä", + "Most used": "Eniten käytetyt", + "Last used": "Viimeksi käytetyt", + "At random": "Satunnaisesti" } } diff --git a/l10n/fr.js b/l10n/fr.js index 307678377..7331b0f10 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Champs du profil enregistrés.", "Save profile fields" : "Enregistrer les champs du profil", "The profile fields could not be loaded." : "Les champs du profil n'ont pas pu être chargés.", - "The profile fields could not be saved." : "Les champs du profil n'ont pas pu être enregistrés." + "The profile fields could not be saved." : "Les champs du profil n'ont pas pu être enregistrés.", + "Forget my usage" : "Oublier mon utilisation", + "Sorted alphabetically" : "Trié par ordre alphabétique", + "Sorted by most used" : "Trié par les plus utilisés", + "Sorted by last used" : "Trié par dernière utilisation", + "Sorted at random" : "Trié au hasard", + "Sort tiles" : "Trier les tuiles", + "By hand" : "À la main", + "Alphabetically" : "Par ordre alphabétique", + "Most used" : "Les plus utilisés", + "Last used" : "Dernière utilisation", + "At random" : "Au hasard" }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/fr.json b/l10n/fr.json index 3c6046a8f..7e5ff6f4d 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Champs du profil enregistrés.", "Save profile fields": "Enregistrer les champs du profil", "The profile fields could not be loaded.": "Les champs du profil n'ont pas pu être chargés.", - "The profile fields could not be saved.": "Les champs du profil n'ont pas pu être enregistrés." + "The profile fields could not be saved.": "Les champs du profil n'ont pas pu être enregistrés.", + "Forget my usage": "Oublier mon utilisation", + "Sorted alphabetically": "Trié par ordre alphabétique", + "Sorted by most used": "Trié par les plus utilisés", + "Sorted by last used": "Trié par dernière utilisation", + "Sorted at random": "Trié au hasard", + "Sort tiles": "Trier les tuiles", + "By hand": "À la main", + "Alphabetically": "Par ordre alphabétique", + "Most used": "Les plus utilisés", + "Last used": "Dernière utilisation", + "At random": "Au hasard" } } diff --git a/l10n/ga.js b/l10n/ga.js index 8ba1d1216..5a43ce7fa 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Réimsí próifíle sábháilte.", "Save profile fields" : "Sábháil réimsí próifíle", "The profile fields could not be loaded." : "Níorbh fhéidir na réimsí próifíle a lódáil.", - "The profile fields could not be saved." : "Níorbh fhéidir na réimsí próifíle a shábháil." + "The profile fields could not be saved." : "Níorbh fhéidir na réimsí próifíle a shábháil.", + "Forget my usage" : "Déan dearmad ar m'úsáid", + "Sorted alphabetically" : "Sórtáilte in ord aibítre", + "Sorted by most used" : "Sórtáilte de réir is mó a úsáidtear", + "Sorted by last used" : "Sórtáilte de réir an úsáid is déanaí", + "Sorted at random" : "Sórtáilte go randamach", + "Sort tiles" : "Sórtáil tíleanna", + "By hand" : "De láimh", + "Alphabetically" : "In ord aibítre", + "Most used" : "Is mó a úsáidtear", + "Last used" : "Úsáidte is déanaí", + "At random" : "Go randamach" }, "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);" ); diff --git a/l10n/ga.json b/l10n/ga.json index 408ceb33b..4f4caf134 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -1291,6 +1291,17 @@ "Profile fields saved.": "Réimsí próifíle sábháilte.", "Save profile fields": "Sábháil réimsí próifíle", "The profile fields could not be loaded.": "Níorbh fhéidir na réimsí próifíle a lódáil.", - "The profile fields could not be saved.": "Níorbh fhéidir na réimsí próifíle a shábháil." + "The profile fields could not be saved.": "Níorbh fhéidir na réimsí próifíle a shábháil.", + "Forget my usage": "Déan dearmad ar m'úsáid", + "Sorted alphabetically": "Sórtáilte in ord aibítre", + "Sorted by most used": "Sórtáilte de réir is mó a úsáidtear", + "Sorted by last used": "Sórtáilte de réir an úsáid is déanaí", + "Sorted at random": "Sórtáilte go randamach", + "Sort tiles": "Sórtáil tíleanna", + "By hand": "De láimh", + "Alphabetically": "In ord aibítre", + "Most used": "Is mó a úsáidtear", + "Last used": "Úsáidte is déanaí", + "At random": "Go randamach" } } diff --git a/l10n/hr.js b/l10n/hr.js index c4b220d61..7bc09558f 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Polja profila su spremljena.", "Save profile fields" : "Spremi polja profila", "The profile fields could not be loaded." : "Polja profila nije bilo moguće učitati.", - "The profile fields could not be saved." : "Polja profila nije bilo moguće spremiti." + "The profile fields could not be saved." : "Polja profila nije bilo moguće spremiti.", + "Forget my usage" : "Zaboravi moje korištenje", + "Sorted alphabetically" : "Poredano abecedno", + "Sorted by most used" : "Poredano po najčešće korištenim", + "Sorted by last used" : "Poredano po posljednje korištenim", + "Sorted at random" : "Poredano nasumično", + "Sort tiles" : "Poredaj pločice", + "By hand" : "Ručno", + "Alphabetically" : "Abecedno", + "Most used" : "Najčešće korišteno", + "Last used" : "Posljednje korišteno", + "At random" : "Nasumično" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/hr.json b/l10n/hr.json index 2c565a477..5abd69a6c 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Polja profila su spremljena.", "Save profile fields": "Spremi polja profila", "The profile fields could not be loaded.": "Polja profila nije bilo moguće učitati.", - "The profile fields could not be saved.": "Polja profila nije bilo moguće spremiti." + "The profile fields could not be saved.": "Polja profila nije bilo moguće spremiti.", + "Forget my usage": "Zaboravi moje korištenje", + "Sorted alphabetically": "Poredano abecedno", + "Sorted by most used": "Poredano po najčešće korištenim", + "Sorted by last used": "Poredano po posljednje korištenim", + "Sorted at random": "Poredano nasumično", + "Sort tiles": "Poredaj pločice", + "By hand": "Ručno", + "Alphabetically": "Abecedno", + "Most used": "Najčešće korišteno", + "Last used": "Posljednje korišteno", + "At random": "Nasumično" } } diff --git a/l10n/hu.js b/l10n/hu.js index fc626aa13..8f267be02 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilmezők mentve.", "Save profile fields" : "Profilmezők mentése", "The profile fields could not be loaded." : "A profilmezőket nem sikerült betölteni.", - "The profile fields could not be saved." : "A profilmezőket nem sikerült menteni." + "The profile fields could not be saved." : "A profilmezőket nem sikerült menteni.", + "Forget my usage" : "Használatom elfelejtése", + "Sorted alphabetically" : "Betűrendbe rendezve", + "Sorted by most used" : "A leggyakrabban használtak szerint rendezve", + "Sorted by last used" : "A legutóbb használtak szerint rendezve", + "Sorted at random" : "Véletlenszerűen rendezve", + "Sort tiles" : "Csempék rendezése", + "By hand" : "Kézzel", + "Alphabetically" : "Betűrendben", + "Most used" : "Leggyakrabban használt", + "Last used" : "Legutóbb használt", + "At random" : "Véletlenszerűen" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/hu.json b/l10n/hu.json index 4ca822358..73771ec3f 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilmezők mentve.", "Save profile fields": "Profilmezők mentése", "The profile fields could not be loaded.": "A profilmezőket nem sikerült betölteni.", - "The profile fields could not be saved.": "A profilmezőket nem sikerült menteni." + "The profile fields could not be saved.": "A profilmezőket nem sikerült menteni.", + "Forget my usage": "Használatom elfelejtése", + "Sorted alphabetically": "Betűrendbe rendezve", + "Sorted by most used": "A leggyakrabban használtak szerint rendezve", + "Sorted by last used": "A legutóbb használtak szerint rendezve", + "Sorted at random": "Véletlenszerűen rendezve", + "Sort tiles": "Csempék rendezése", + "By hand": "Kézzel", + "Alphabetically": "Betűrendben", + "Most used": "Leggyakrabban használt", + "Last used": "Legutóbb használt", + "At random": "Véletlenszerűen" } } diff --git a/l10n/is.js b/l10n/is.js index 64029bd3c..90bb6f0a6 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Prófílreitir vistaðir.", "Save profile fields" : "Vista prófílreiti", "The profile fields could not be loaded." : "Ekki tókst að hlaða inn prófílreitunum.", - "The profile fields could not be saved." : "Ekki tókst að vista prófílreitina." + "The profile fields could not be saved." : "Ekki tókst að vista prófílreitina.", + "Forget my usage" : "Gleyma notkun minni", + "Sorted alphabetically" : "Raðað í stafrófsröð", + "Sorted by most used" : "Raðað eftir mest notuðu", + "Sorted by last used" : "Raðað eftir síðast notuðu", + "Sorted at random" : "Raðað af handahófi", + "Sort tiles" : "Raða reitum", + "By hand" : "Handvirkt", + "Alphabetically" : "Í stafrófsröð", + "Most used" : "Mest notað", + "Last used" : "Síðast notað", + "At random" : "Af handahófi" }, "nplurals=2; plural=(n%10!=1 || n%100==11);" ); diff --git a/l10n/is.json b/l10n/is.json index de529d623..3b278cd6e 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Prófílreitir vistaðir.", "Save profile fields": "Vista prófílreiti", "The profile fields could not be loaded.": "Ekki tókst að hlaða inn prófílreitunum.", - "The profile fields could not be saved.": "Ekki tókst að vista prófílreitina." + "The profile fields could not be saved.": "Ekki tókst að vista prófílreitina.", + "Forget my usage": "Gleyma notkun minni", + "Sorted alphabetically": "Raðað í stafrófsröð", + "Sorted by most used": "Raðað eftir mest notuðu", + "Sorted by last used": "Raðað eftir síðast notuðu", + "Sorted at random": "Raðað af handahófi", + "Sort tiles": "Raða reitum", + "By hand": "Handvirkt", + "Alphabetically": "Í stafrófsröð", + "Most used": "Mest notað", + "Last used": "Síðast notað", + "At random": "Af handahófi" } } diff --git a/l10n/it.js b/l10n/it.js index ab0446c0d..084ad08ea 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Campi del profilo salvati.", "Save profile fields" : "Salva campi del profilo", "The profile fields could not be loaded." : "Non è stato possibile caricare i campi del profilo.", - "The profile fields could not be saved." : "Non è stato possibile salvare i campi del profilo." + "The profile fields could not be saved." : "Non è stato possibile salvare i campi del profilo.", + "Forget my usage" : "Dimentica il mio utilizzo", + "Sorted alphabetically" : "Ordinato alfabeticamente", + "Sorted by most used" : "Ordinato per più usati", + "Sorted by last used" : "Ordinato per ultimo utilizzo", + "Sorted at random" : "Ordinato a caso", + "Sort tiles" : "Ordina riquadri", + "By hand" : "A mano", + "Alphabetically" : "In ordine alfabetico", + "Most used" : "Più usati", + "Last used" : "Ultimo utilizzo", + "At random" : "A caso" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/it.json b/l10n/it.json index fa3dd24fa..0bf896a8c 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Campi del profilo salvati.", "Save profile fields": "Salva campi del profilo", "The profile fields could not be loaded.": "Non è stato possibile caricare i campi del profilo.", - "The profile fields could not be saved.": "Non è stato possibile salvare i campi del profilo." + "The profile fields could not be saved.": "Non è stato possibile salvare i campi del profilo.", + "Forget my usage": "Dimentica il mio utilizzo", + "Sorted alphabetically": "Ordinato alfabeticamente", + "Sorted by most used": "Ordinato per più usati", + "Sorted by last used": "Ordinato per ultimo utilizzo", + "Sorted at random": "Ordinato a caso", + "Sort tiles": "Ordina riquadri", + "By hand": "A mano", + "Alphabetically": "In ordine alfabetico", + "Most used": "Più usati", + "Last used": "Ultimo utilizzo", + "At random": "A caso" } } diff --git a/l10n/lb.js b/l10n/lb.js index 84faec3ed..57ee7d502 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -1312,7 +1312,18 @@ OC.L10N.register( "Profile fields saved." : "Profilfelder gespäichert.", "Save profile fields" : "Profilfelder späicheren", "The profile fields could not be loaded." : "D'Profilfelder konnten net geluede ginn.", - "The profile fields could not be saved." : "D'Profilfelder konnten net gespäichert ginn." + "The profile fields could not be saved." : "D'Profilfelder konnten net gespäichert ginn.", + "Forget my usage" : "Meng Notzung vergiessen", + "Sorted alphabetically" : "Alphabetesch sortéiert", + "Sorted by most used" : "No der meeschter Notzung sortéiert", + "Sorted by last used" : "No der leschter Notzung sortéiert", + "Sorted at random" : "Zoufälleg sortéiert", + "Sort tiles" : "Kachelen sortéieren", + "By hand" : "Vun Hand", + "Alphabetically" : "Alphabetesch", + "Most used" : "Am meeschte benotzt", + "Last used" : "Lescht benotzt", + "At random" : "Zoufälleg" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/lb.json b/l10n/lb.json index e6fc3747f..530df9f16 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilfelder gespäichert.", "Save profile fields": "Profilfelder späicheren", "The profile fields could not be loaded.": "D'Profilfelder konnten net geluede ginn.", - "The profile fields could not be saved.": "D'Profilfelder konnten net gespäichert ginn." + "The profile fields could not be saved.": "D'Profilfelder konnten net gespäichert ginn.", + "Forget my usage": "Meng Notzung vergiessen", + "Sorted alphabetically": "Alphabetesch sortéiert", + "Sorted by most used": "No der meeschter Notzung sortéiert", + "Sorted by last used": "No der leschter Notzung sortéiert", + "Sorted at random": "Zoufälleg sortéiert", + "Sort tiles": "Kachelen sortéieren", + "By hand": "Vun Hand", + "Alphabetically": "Alphabetesch", + "Most used": "Am meeschte benotzt", + "Last used": "Lescht benotzt", + "At random": "Zoufälleg" } } diff --git a/l10n/lt.js b/l10n/lt.js index 8017d3ed4..b9f290070 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilio laukai išsaugoti.", "Save profile fields" : "Išsaugoti profilio laukus", "The profile fields could not be loaded." : "Nepavyko įkelti profilio laukų.", - "The profile fields could not be saved." : "Nepavyko išsaugoti profilio laukų." + "The profile fields could not be saved." : "Nepavyko išsaugoti profilio laukų.", + "Forget my usage" : "Pamiršti mano naudojimą", + "Sorted alphabetically" : "Surikiuota abėcėlės tvarka", + "Sorted by most used" : "Surikiuota pagal dažniausiai naudojamus", + "Sorted by last used" : "Surikiuota pagal paskutinį naudojimą", + "Sorted at random" : "Surikiuota atsitiktinai", + "Sort tiles" : "Plytelių rikiavimas", + "By hand" : "Rankiniu būdu", + "Alphabetically" : "Abėcėlės tvarka", + "Most used" : "Dažniausiai naudojami", + "Last used" : "Paskutiniai naudoti", + "At random" : "Atsitiktinai" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/lt.json b/l10n/lt.json index b0bd59a86..6a77144f8 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Profilio laukai išsaugoti.", "Save profile fields": "Išsaugoti profilio laukus", "The profile fields could not be loaded.": "Nepavyko įkelti profilio laukų.", - "The profile fields could not be saved.": "Nepavyko išsaugoti profilio laukų." + "The profile fields could not be saved.": "Nepavyko išsaugoti profilio laukų.", + "Forget my usage": "Pamiršti mano naudojimą", + "Sorted alphabetically": "Surikiuota abėcėlės tvarka", + "Sorted by most used": "Surikiuota pagal dažniausiai naudojamus", + "Sorted by last used": "Surikiuota pagal paskutinį naudojimą", + "Sorted at random": "Surikiuota atsitiktinai", + "Sort tiles": "Plytelių rikiavimas", + "By hand": "Rankiniu būdu", + "Alphabetically": "Abėcėlės tvarka", + "Most used": "Dažniausiai naudojami", + "Last used": "Paskutiniai naudoti", + "At random": "Atsitiktinai" } } diff --git a/l10n/lv.js b/l10n/lv.js index 967b9beb5..375685874 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profila lauki saglabāti.", "Save profile fields" : "Saglabāt profila laukus", "The profile fields could not be loaded." : "Neizdevās ielādēt profila laukus.", - "The profile fields could not be saved." : "Neizdevās saglabāt profila laukus." + "The profile fields could not be saved." : "Neizdevās saglabāt profila laukus.", + "Forget my usage" : "Aizmirst manu lietojumu", + "Sorted alphabetically" : "Kārtots alfabētiski", + "Sorted by most used" : "Kārtots pēc visbiežāk lietotajiem", + "Sorted by last used" : "Kārtots pēc pēdējās lietošanas", + "Sorted at random" : "Kārtots nejauši", + "Sort tiles" : "Flīžu kārtošana", + "By hand" : "Manuāli", + "Alphabetically" : "Alfabētiski", + "Most used" : "Visbiežāk lietotie", + "Last used" : "Pēdējie lietotie", + "At random" : "Nejauši" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" ); diff --git a/l10n/lv.json b/l10n/lv.json index e1a6d207b..ddc13ea1d 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Profila lauki saglabāti.", "Save profile fields": "Saglabāt profila laukus", "The profile fields could not be loaded.": "Neizdevās ielādēt profila laukus.", - "The profile fields could not be saved.": "Neizdevās saglabāt profila laukus." + "The profile fields could not be saved.": "Neizdevās saglabāt profila laukus.", + "Forget my usage": "Aizmirst manu lietojumu", + "Sorted alphabetically": "Kārtots alfabētiski", + "Sorted by most used": "Kārtots pēc visbiežāk lietotajiem", + "Sorted by last used": "Kārtots pēc pēdējās lietošanas", + "Sorted at random": "Kārtots nejauši", + "Sort tiles": "Flīžu kārtošana", + "By hand": "Manuāli", + "Alphabetically": "Alfabētiski", + "Most used": "Visbiežāk lietotie", + "Last used": "Pēdējie lietotie", + "At random": "Nejauši" } } diff --git a/l10n/mk.js b/l10n/mk.js index e7f077905..1309b932d 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -1312,7 +1312,18 @@ OC.L10N.register( "Profile fields saved." : "Полињата на профилот се зачувани.", "Save profile fields" : "Зачувај полиња на профилот", "The profile fields could not be loaded." : "Полињата на профилот не можеа да се вчитаат.", - "The profile fields could not be saved." : "Полињата на профилот не можеа да се зачуваат." + "The profile fields could not be saved." : "Полињата на профилот не можеа да се зачуваат.", + "Forget my usage" : "Заборави ја мојата употреба", + "Sorted alphabetically" : "Подредено по азбучен ред", + "Sorted by most used" : "Подредено по најкористени", + "Sorted by last used" : "Подредено по последно користени", + "Sorted at random" : "Подредено по случаен избор", + "Sort tiles" : "Подредување на плочките", + "By hand" : "Рачно", + "Alphabetically" : "Азбучно", + "Most used" : "Најкористени", + "Last used" : "Последно користени", + "At random" : "По случаен избор" }, "nplurals=2; plural=(n%10==1 && n%100!=11 ? 0 : 1);" ); diff --git a/l10n/mk.json b/l10n/mk.json index 967e1c28d..ab48acee8 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Полињата на профилот се зачувани.", "Save profile fields": "Зачувај полиња на профилот", "The profile fields could not be loaded.": "Полињата на профилот не можеа да се вчитаат.", - "The profile fields could not be saved.": "Полињата на профилот не можеа да се зачуваат." + "The profile fields could not be saved.": "Полињата на профилот не можеа да се зачуваат.", + "Forget my usage": "Заборави ја мојата употреба", + "Sorted alphabetically": "Подредено по азбучен ред", + "Sorted by most used": "Подредено по најкористени", + "Sorted by last used": "Подредено по последно користени", + "Sorted at random": "Подредено по случаен избор", + "Sort tiles": "Подредување на плочките", + "By hand": "Рачно", + "Alphabetically": "Азбучно", + "Most used": "Најкористени", + "Last used": "Последно користени", + "At random": "По случаен избор" } } diff --git a/l10n/mt.js b/l10n/mt.js index 21ab65e16..efeb4b39b 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Il-oqsma tal-profil ġew salvati.", "Save profile fields" : "Issejvja l-oqsma tal-profil", "The profile fields could not be loaded." : "Il-oqsma tal-profil ma setgħux jitgħabbew.", - "The profile fields could not be saved." : "Il-oqsma tal-profil ma setgħux jiġu salvati." + "The profile fields could not be saved." : "Il-oqsma tal-profil ma setgħux jiġu salvati.", + "Forget my usage" : "Insa l-użu tiegħi", + "Sorted alphabetically" : "Issortjat alfabetikament", + "Sorted by most used" : "Issortjat skont l-aktar użati", + "Sorted by last used" : "Issortjat skont l-aħħar użu", + "Sorted at random" : "Issortjat b'mod każwali", + "Sort tiles" : "Issortja l-madum", + "By hand" : "Bl-idejn", + "Alphabetically" : "Alfabetikament", + "Most used" : "L-aktar użati", + "Last used" : "L-aħħar użati", + "At random" : "B'mod każwali" }, "nplurals=4; plural=(n==1 ? 0 : n==0 || (n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20) ? 2 : 3);" ); diff --git a/l10n/mt.json b/l10n/mt.json index ff0697add..65e7e70ea 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -1290,6 +1290,17 @@ "Profile fields saved.": "Il-oqsma tal-profil ġew salvati.", "Save profile fields": "Issejvja l-oqsma tal-profil", "The profile fields could not be loaded.": "Il-oqsma tal-profil ma setgħux jitgħabbew.", - "The profile fields could not be saved.": "Il-oqsma tal-profil ma setgħux jiġu salvati." + "The profile fields could not be saved.": "Il-oqsma tal-profil ma setgħux jiġu salvati.", + "Forget my usage": "Insa l-użu tiegħi", + "Sorted alphabetically": "Issortjat alfabetikament", + "Sorted by most used": "Issortjat skont l-aktar użati", + "Sorted by last used": "Issortjat skont l-aħħar użu", + "Sorted at random": "Issortjat b'mod każwali", + "Sort tiles": "Issortja l-madum", + "By hand": "Bl-idejn", + "Alphabetically": "Alfabetikament", + "Most used": "L-aktar użati", + "Last used": "L-aħħar użati", + "At random": "B'mod każwali" } } diff --git a/l10n/nb.js b/l10n/nb.js index 226a19ce8..cadeff1a3 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilfeltene er lagret.", "Save profile fields" : "Lagre profilfelt", "The profile fields could not be loaded." : "Profilfeltene kunne ikke lastes inn.", - "The profile fields could not be saved." : "Profilfeltene kunne ikke lagres." + "The profile fields could not be saved." : "Profilfeltene kunne ikke lagres.", + "Forget my usage" : "Glem bruken min", + "Sorted alphabetically" : "Sortert alfabetisk", + "Sorted by most used" : "Sortert etter mest brukt", + "Sorted by last used" : "Sortert etter sist brukt", + "Sorted at random" : "Sortert tilfeldig", + "Sort tiles" : "Sorter fliser", + "By hand" : "Manuelt", + "Alphabetically" : "Alfabetisk", + "Most used" : "Mest brukt", + "Last used" : "Sist brukt", + "At random" : "Tilfeldig" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nb.json b/l10n/nb.json index 60cb3f614..8059022ed 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilfeltene er lagret.", "Save profile fields": "Lagre profilfelt", "The profile fields could not be loaded.": "Profilfeltene kunne ikke lastes inn.", - "The profile fields could not be saved.": "Profilfeltene kunne ikke lagres." + "The profile fields could not be saved.": "Profilfeltene kunne ikke lagres.", + "Forget my usage": "Glem bruken min", + "Sorted alphabetically": "Sortert alfabetisk", + "Sorted by most used": "Sortert etter mest brukt", + "Sorted by last used": "Sortert etter sist brukt", + "Sorted at random": "Sortert tilfeldig", + "Sort tiles": "Sorter fliser", + "By hand": "Manuelt", + "Alphabetically": "Alfabetisk", + "Most used": "Mest brukt", + "Last used": "Sist brukt", + "At random": "Tilfeldig" } } diff --git a/l10n/nl.js b/l10n/nl.js index 59f9b10fa..8648eb478 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -1371,7 +1371,18 @@ OC.L10N.register( "Profile fields saved." : "Profielvelden opgeslagen.", "Save profile fields" : "Profielvelden opslaan", "The profile fields could not be loaded." : "De profielvelden konden niet worden geladen.", - "The profile fields could not be saved." : "De profielvelden konden niet worden opgeslagen." + "The profile fields could not be saved." : "De profielvelden konden niet worden opgeslagen.", + "Forget my usage" : "Mijn gebruik vergeten", + "Sorted alphabetically" : "Alfabetisch gesorteerd", + "Sorted by most used" : "Gesorteerd op meest gebruikt", + "Sorted by last used" : "Gesorteerd op laatst gebruikt", + "Sorted at random" : "Willekeurig gesorteerd", + "Sort tiles" : "Tegels sorteren", + "By hand" : "Handmatig", + "Alphabetically" : "Alfabetisch", + "Most used" : "Meest gebruikt", + "Last used" : "Laatst gebruikt", + "At random" : "Willekeurig" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nl.json b/l10n/nl.json index c1070d47a..e774d1823 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -1348,6 +1348,17 @@ "Profile fields saved.": "Profielvelden opgeslagen.", "Save profile fields": "Profielvelden opslaan", "The profile fields could not be loaded.": "De profielvelden konden niet worden geladen.", - "The profile fields could not be saved.": "De profielvelden konden niet worden opgeslagen." + "The profile fields could not be saved.": "De profielvelden konden niet worden opgeslagen.", + "Forget my usage": "Mijn gebruik vergeten", + "Sorted alphabetically": "Alfabetisch gesorteerd", + "Sorted by most used": "Gesorteerd op meest gebruikt", + "Sorted by last used": "Gesorteerd op laatst gebruikt", + "Sorted at random": "Willekeurig gesorteerd", + "Sort tiles": "Tegels sorteren", + "By hand": "Handmatig", + "Alphabetically": "Alfabetisch", + "Most used": "Meest gebruikt", + "Last used": "Laatst gebruikt", + "At random": "Willekeurig" } } diff --git a/l10n/pl.js b/l10n/pl.js index 7754a5c2a..285dce17a 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Pola profilu zapisane.", "Save profile fields" : "Zapisz pola profilu", "The profile fields could not be loaded." : "Nie udało się wczytać pól profilu.", - "The profile fields could not be saved." : "Nie udało się zapisać pól profilu." + "The profile fields could not be saved." : "Nie udało się zapisać pól profilu.", + "Forget my usage" : "Zapomnij moje użycie", + "Sorted alphabetically" : "Posortowane alfabetycznie", + "Sorted by most used" : "Posortowane według najczęściej używanych", + "Sorted by last used" : "Posortowane według ostatnio używanych", + "Sorted at random" : "Posortowane losowo", + "Sort tiles" : "Sortuj kafelki", + "By hand" : "Ręcznie", + "Alphabetically" : "Alfabetycznie", + "Most used" : "Najczęściej używane", + "Last used" : "Ostatnio używane", + "At random" : "Losowo" }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/pl.json b/l10n/pl.json index 60814ee56..e82900981 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Pola profilu zapisane.", "Save profile fields": "Zapisz pola profilu", "The profile fields could not be loaded.": "Nie udało się wczytać pól profilu.", - "The profile fields could not be saved.": "Nie udało się zapisać pól profilu." + "The profile fields could not be saved.": "Nie udało się zapisać pól profilu.", + "Forget my usage": "Zapomnij moje użycie", + "Sorted alphabetically": "Posortowane alfabetycznie", + "Sorted by most used": "Posortowane według najczęściej używanych", + "Sorted by last used": "Posortowane według ostatnio używanych", + "Sorted at random": "Posortowane losowo", + "Sort tiles": "Sortuj kafelki", + "By hand": "Ręcznie", + "Alphabetically": "Alfabetycznie", + "Most used": "Najczęściej używane", + "Last used": "Ostatnio używane", + "At random": "Losowo" } } diff --git a/l10n/pt.js b/l10n/pt.js index 4cdf7ed03..e515e3e67 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Campos do perfil guardados.", "Save profile fields" : "Guardar campos do perfil", "The profile fields could not be loaded." : "Não foi possível carregar os campos do perfil.", - "The profile fields could not be saved." : "Não foi possível guardar os campos do perfil." + "The profile fields could not be saved." : "Não foi possível guardar os campos do perfil.", + "Forget my usage" : "Esquecer a minha utilização", + "Sorted alphabetically" : "Ordenado alfabeticamente", + "Sorted by most used" : "Ordenado pelos mais usados", + "Sorted by last used" : "Ordenado pelo último uso", + "Sorted at random" : "Ordenado aleatoriamente", + "Sort tiles" : "Ordenar mosaicos", + "By hand" : "Manualmente", + "Alphabetically" : "Alfabeticamente", + "Most used" : "Mais usados", + "Last used" : "Último uso", + "At random" : "Aleatoriamente" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/pt.json b/l10n/pt.json index 41b8a8f8d..90c186688 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Campos do perfil guardados.", "Save profile fields": "Guardar campos do perfil", "The profile fields could not be loaded.": "Não foi possível carregar os campos do perfil.", - "The profile fields could not be saved.": "Não foi possível guardar os campos do perfil." + "The profile fields could not be saved.": "Não foi possível guardar os campos do perfil.", + "Forget my usage": "Esquecer a minha utilização", + "Sorted alphabetically": "Ordenado alfabeticamente", + "Sorted by most used": "Ordenado pelos mais usados", + "Sorted by last used": "Ordenado pelo último uso", + "Sorted at random": "Ordenado aleatoriamente", + "Sort tiles": "Ordenar mosaicos", + "By hand": "Manualmente", + "Alphabetically": "Alfabeticamente", + "Most used": "Mais usados", + "Last used": "Último uso", + "At random": "Aleatoriamente" } } diff --git a/l10n/rm.js b/l10n/rm.js index 457c0a42f..9453ed16a 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Champs dal profil memorisads.", "Save profile fields" : "Memorisar ils champs dal profil", "The profile fields could not be loaded." : "I n'è betg reussì da chargiar ils champs dal profil.", - "The profile fields could not be saved." : "I n'è betg reussì da memorisar ils champs dal profil." + "The profile fields could not be saved." : "I n'è betg reussì da memorisar ils champs dal profil.", + "Forget my usage" : "Emblidar mes diever", + "Sorted alphabetically" : "Zavrà alfabeticamain", + "Sorted by most used" : "Zavrà tenor il diever il pli frequent", + "Sorted by last used" : "Zavrà tenor l'ultim diever", + "Sorted at random" : "Zavrà a chaschun", + "Sort tiles" : "Zavrar ils quadrels", + "By hand" : "A maun", + "Alphabetically" : "Alfabeticamain", + "Most used" : "Il pli savens duvrà", + "Last used" : "Duvrà l'ultim", + "At random" : "A chaschun" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/rm.json b/l10n/rm.json index 56794d514..566dd6a70 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Champs dal profil memorisads.", "Save profile fields": "Memorisar ils champs dal profil", "The profile fields could not be loaded.": "I n'è betg reussì da chargiar ils champs dal profil.", - "The profile fields could not be saved.": "I n'è betg reussì da memorisar ils champs dal profil." + "The profile fields could not be saved.": "I n'è betg reussì da memorisar ils champs dal profil.", + "Forget my usage": "Emblidar mes diever", + "Sorted alphabetically": "Zavrà alfabeticamain", + "Sorted by most used": "Zavrà tenor il diever il pli frequent", + "Sorted by last used": "Zavrà tenor l'ultim diever", + "Sorted at random": "Zavrà a chaschun", + "Sort tiles": "Zavrar ils quadrels", + "By hand": "A maun", + "Alphabetically": "Alfabeticamain", + "Most used": "Il pli savens duvrà", + "Last used": "Duvrà l'ultim", + "At random": "A chaschun" } } diff --git a/l10n/ro.js b/l10n/ro.js index 350c650fa..358d66e1a 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Câmpurile de profil au fost salvate.", "Save profile fields" : "Salvează câmpurile de profil", "The profile fields could not be loaded." : "Câmpurile de profil nu au putut fi încărcate.", - "The profile fields could not be saved." : "Câmpurile de profil nu au putut fi salvate." + "The profile fields could not be saved." : "Câmpurile de profil nu au putut fi salvate.", + "Forget my usage" : "Uită utilizarea mea", + "Sorted alphabetically" : "Sortat alfabetic", + "Sorted by most used" : "Sortat după cele mai folosite", + "Sorted by last used" : "Sortat după ultima utilizare", + "Sorted at random" : "Sortat aleatoriu", + "Sort tiles" : "Sortează dalele", + "By hand" : "Manual", + "Alphabetically" : "Alfabetic", + "Most used" : "Cele mai folosite", + "Last used" : "Ultima utilizare", + "At random" : "Aleatoriu" }, "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);" ); diff --git a/l10n/ro.json b/l10n/ro.json index bf0d96182..4ce56bcf0 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Câmpurile de profil au fost salvate.", "Save profile fields": "Salvează câmpurile de profil", "The profile fields could not be loaded.": "Câmpurile de profil nu au putut fi încărcate.", - "The profile fields could not be saved.": "Câmpurile de profil nu au putut fi salvate." + "The profile fields could not be saved.": "Câmpurile de profil nu au putut fi salvate.", + "Forget my usage": "Uită utilizarea mea", + "Sorted alphabetically": "Sortat alfabetic", + "Sorted by most used": "Sortat după cele mai folosite", + "Sorted by last used": "Sortat după ultima utilizare", + "Sorted at random": "Sortat aleatoriu", + "Sort tiles": "Sortează dalele", + "By hand": "Manual", + "Alphabetically": "Alfabetic", + "Most used": "Cele mai folosite", + "Last used": "Ultima utilizare", + "At random": "Aleatoriu" } } diff --git a/l10n/ru.js b/l10n/ru.js index 6eb71e4a6..215c42ce6 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Поля профиля сохранены.", "Save profile fields" : "Сохранить поля профиля", "The profile fields could not be loaded." : "Не удалось загрузить поля профиля.", - "The profile fields could not be saved." : "Не удалось сохранить поля профиля." + "The profile fields could not be saved." : "Не удалось сохранить поля профиля.", + "Forget my usage" : "Забыть моё использование", + "Sorted alphabetically" : "Отсортировано по алфавиту", + "Sorted by most used" : "Отсортировано по частоте использования", + "Sorted by last used" : "Отсортировано по последнему использованию", + "Sorted at random" : "Отсортировано случайно", + "Sort tiles" : "Сортировка плиток", + "By hand" : "Вручную", + "Alphabetically" : "По алфавиту", + "Most used" : "Самые используемые", + "Last used" : "Последние использованные", + "At random" : "Случайно" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/ru.json b/l10n/ru.json index 9958f3d25..752062dab 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Поля профиля сохранены.", "Save profile fields": "Сохранить поля профиля", "The profile fields could not be loaded.": "Не удалось загрузить поля профиля.", - "The profile fields could not be saved.": "Не удалось сохранить поля профиля." + "The profile fields could not be saved.": "Не удалось сохранить поля профиля.", + "Forget my usage": "Забыть моё использование", + "Sorted alphabetically": "Отсортировано по алфавиту", + "Sorted by most used": "Отсортировано по частоте использования", + "Sorted by last used": "Отсортировано по последнему использованию", + "Sorted at random": "Отсортировано случайно", + "Sort tiles": "Сортировка плиток", + "By hand": "Вручную", + "Alphabetically": "По алфавиту", + "Most used": "Самые используемые", + "Last used": "Последние использованные", + "At random": "Случайно" } } diff --git a/l10n/sk.js b/l10n/sk.js index 972c0e9cc..dc9930564 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Polia profilu sú uložené.", "Save profile fields" : "Uložiť polia profilu", "The profile fields could not be loaded." : "Polia profilu sa nepodarilo načítať.", - "The profile fields could not be saved." : "Polia profilu sa nepodarilo uložiť." + "The profile fields could not be saved." : "Polia profilu sa nepodarilo uložiť.", + "Forget my usage" : "Zabudnúť moje používanie", + "Sorted alphabetically" : "Zoradené abecedne", + "Sorted by most used" : "Zoradené podľa najpoužívanejších", + "Sorted by last used" : "Zoradené podľa naposledy použitých", + "Sorted at random" : "Zoradené náhodne", + "Sort tiles" : "Zoradenie dlaždíc", + "By hand" : "Ručne", + "Alphabetically" : "Abecedne", + "Most used" : "Najpoužívanejšie", + "Last used" : "Naposledy použité", + "At random" : "Náhodne" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/sk.json b/l10n/sk.json index 84474ab28..f4ad5b771 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Polia profilu sú uložené.", "Save profile fields": "Uložiť polia profilu", "The profile fields could not be loaded.": "Polia profilu sa nepodarilo načítať.", - "The profile fields could not be saved.": "Polia profilu sa nepodarilo uložiť." + "The profile fields could not be saved.": "Polia profilu sa nepodarilo uložiť.", + "Forget my usage": "Zabudnúť moje používanie", + "Sorted alphabetically": "Zoradené abecedne", + "Sorted by most used": "Zoradené podľa najpoužívanejších", + "Sorted by last used": "Zoradené podľa naposledy použitých", + "Sorted at random": "Zoradené náhodne", + "Sort tiles": "Zoradenie dlaždíc", + "By hand": "Ručne", + "Alphabetically": "Abecedne", + "Most used": "Najpoužívanejšie", + "Last used": "Naposledy použité", + "At random": "Náhodne" } } diff --git a/l10n/sl.js b/l10n/sl.js index 368c7922b..aae665454 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Polja profila so shranjena.", "Save profile fields" : "Shrani polja profila", "The profile fields could not be loaded." : "Polj profila ni bilo mogoče naložiti.", - "The profile fields could not be saved." : "Polj profila ni bilo mogoče shraniti." + "The profile fields could not be saved." : "Polj profila ni bilo mogoče shraniti.", + "Forget my usage" : "Pozabi mojo uporabo", + "Sorted alphabetically" : "Razvrščeno po abecedi", + "Sorted by most used" : "Razvrščeno po najpogosteje uporabljenih", + "Sorted by last used" : "Razvrščeno po zadnji uporabi", + "Sorted at random" : "Razvrščeno naključno", + "Sort tiles" : "Razvrsti ploščice", + "By hand" : "Ročno", + "Alphabetically" : "Po abecedi", + "Most used" : "Najpogosteje uporabljeno", + "Last used" : "Nazadnje uporabljeno", + "At random" : "Naključno" }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" ); diff --git a/l10n/sl.json b/l10n/sl.json index 4fadf33d5..a46e1bfe9 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -1290,6 +1290,17 @@ "Profile fields saved.": "Polja profila so shranjena.", "Save profile fields": "Shrani polja profila", "The profile fields could not be loaded.": "Polj profila ni bilo mogoče naložiti.", - "The profile fields could not be saved.": "Polj profila ni bilo mogoče shraniti." + "The profile fields could not be saved.": "Polj profila ni bilo mogoče shraniti.", + "Forget my usage": "Pozabi mojo uporabo", + "Sorted alphabetically": "Razvrščeno po abecedi", + "Sorted by most used": "Razvrščeno po najpogosteje uporabljenih", + "Sorted by last used": "Razvrščeno po zadnji uporabi", + "Sorted at random": "Razvrščeno naključno", + "Sort tiles": "Razvrsti ploščice", + "By hand": "Ročno", + "Alphabetically": "Po abecedi", + "Most used": "Najpogosteje uporabljeno", + "Last used": "Nazadnje uporabljeno", + "At random": "Naključno" } } diff --git a/l10n/sq.js b/l10n/sq.js index cdcf747d1..0e6961f3d 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Fushat e profilit u ruajtën.", "Save profile fields" : "Ruaj fushat e profilit", "The profile fields could not be loaded." : "Fushat e profilit nuk u ngarkuan.", - "The profile fields could not be saved." : "Fushat e profilit nuk u ruajtën." + "The profile fields could not be saved." : "Fushat e profilit nuk u ruajtën.", + "Forget my usage" : "Harro përdorimin tim", + "Sorted alphabetically" : "Renditur sipas alfabetit", + "Sorted by most used" : "Renditur sipas më të përdorurave", + "Sorted by last used" : "Renditur sipas përdorimit të fundit", + "Sorted at random" : "Renditur rastësisht", + "Sort tiles" : "Rendit pllakat", + "By hand" : "Me dorë", + "Alphabetically" : "Sipas alfabetit", + "Most used" : "Më të përdorurat", + "Last used" : "Përdorur së fundi", + "At random" : "Rastësisht" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sq.json b/l10n/sq.json index 1fb6fecd0..558053019 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Fushat e profilit u ruajtën.", "Save profile fields": "Ruaj fushat e profilit", "The profile fields could not be loaded.": "Fushat e profilit nuk u ngarkuan.", - "The profile fields could not be saved.": "Fushat e profilit nuk u ruajtën." + "The profile fields could not be saved.": "Fushat e profilit nuk u ruajtën.", + "Forget my usage": "Harro përdorimin tim", + "Sorted alphabetically": "Renditur sipas alfabetit", + "Sorted by most used": "Renditur sipas më të përdorurave", + "Sorted by last used": "Renditur sipas përdorimit të fundit", + "Sorted at random": "Renditur rastësisht", + "Sort tiles": "Rendit pllakat", + "By hand": "Me dorë", + "Alphabetically": "Sipas alfabetit", + "Most used": "Më të përdorurat", + "Last used": "Përdorur së fundi", + "At random": "Rastësisht" } } diff --git a/l10n/sr.js b/l10n/sr.js index 612b24d10..d8bca7a02 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -1312,6 +1312,17 @@ OC.L10N.register( "Profile fields saved." : "Поља профила су сачувана.", "Save profile fields" : "Сачувај поља профила", "The profile fields could not be loaded." : "Поља профила нису могла да се учитају.", - "The profile fields could not be saved." : "Поља профила нису могла да се сачувају." + "The profile fields could not be saved." : "Поља профила нису могла да се сачувају.", + "Forget my usage" : "Заборави моје коришћење", + "Sorted alphabetically" : "Поређано абецедно", + "Sorted by most used" : "Поређано по најкоришћенијим", + "Sorted by last used" : "Поређано по последње коришћеним", + "Sorted at random" : "Поређано насумично", + "Sort tiles" : "Поређај плочице", + "By hand" : "Ручно", + "Alphabetically" : "Абецедно", + "Most used" : "Најкоришћеније", + "Last used" : "Последње коришћено", + "At random" : "Насумично" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/sr.json b/l10n/sr.json index fa807ae69..9fc9ef3b4 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Поља профила су сачувана.", "Save profile fields": "Сачувај поља профила", "The profile fields could not be loaded.": "Поља профила нису могла да се учитају.", - "The profile fields could not be saved.": "Поља профила нису могла да се сачувају." + "The profile fields could not be saved.": "Поља профила нису могла да се сачувају.", + "Forget my usage": "Заборави моје коришћење", + "Sorted alphabetically": "Поређано абецедно", + "Sorted by most used": "Поређано по најкоришћенијим", + "Sorted by last used": "Поређано по последње коришћеним", + "Sorted at random": "Поређано насумично", + "Sort tiles": "Поређај плочице", + "By hand": "Ручно", + "Alphabetically": "Абецедно", + "Most used": "Најкоришћеније", + "Last used": "Последње коришћено", + "At random": "Насумично" } } diff --git a/l10n/sv.js b/l10n/sv.js index b00492b8a..9f2c290bf 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profilfälten har sparats.", "Save profile fields" : "Spara profilfält", "The profile fields could not be loaded." : "Profilfälten kunde inte läsas in.", - "The profile fields could not be saved." : "Profilfälten kunde inte sparas." + "The profile fields could not be saved." : "Profilfälten kunde inte sparas.", + "Forget my usage" : "Glöm min användning", + "Sorted alphabetically" : "Sorterat i bokstavsordning", + "Sorted by most used" : "Sorterat efter mest använda", + "Sorted by last used" : "Sorterat efter senast använda", + "Sorted at random" : "Sorterat slumpmässigt", + "Sort tiles" : "Sortera paneler", + "By hand" : "Manuellt", + "Alphabetically" : "I bokstavsordning", + "Most used" : "Mest använda", + "Last used" : "Senast använda", + "At random" : "Slumpmässigt" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sv.json b/l10n/sv.json index 01609597c..3a86552d0 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profilfälten har sparats.", "Save profile fields": "Spara profilfält", "The profile fields could not be loaded.": "Profilfälten kunde inte läsas in.", - "The profile fields could not be saved.": "Profilfälten kunde inte sparas." + "The profile fields could not be saved.": "Profilfälten kunde inte sparas.", + "Forget my usage": "Glöm min användning", + "Sorted alphabetically": "Sorterat i bokstavsordning", + "Sorted by most used": "Sorterat efter mest använda", + "Sorted by last used": "Sorterat efter senast använda", + "Sorted at random": "Sorterat slumpmässigt", + "Sort tiles": "Sortera paneler", + "By hand": "Manuellt", + "Alphabetically": "I bokstavsordning", + "Most used": "Mest använda", + "Last used": "Senast använda", + "At random": "Slumpmässigt" } } diff --git a/l10n/tr.js b/l10n/tr.js index c6aedbaa1..3089b26ee 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Profil alanları kaydedildi.", "Save profile fields" : "Profil alanlarını kaydet", "The profile fields could not be loaded." : "Profil alanları yüklenemedi.", - "The profile fields could not be saved." : "Profil alanları kaydedilemedi." + "The profile fields could not be saved." : "Profil alanları kaydedilemedi.", + "Forget my usage" : "Kullanımımı unut", + "Sorted alphabetically" : "Alfabetik sıralandı", + "Sorted by most used" : "En çok kullanılana göre sıralandı", + "Sorted by last used" : "Son kullanılana göre sıralandı", + "Sorted at random" : "Rastgele sıralandı", + "Sort tiles" : "Kutucukları sırala", + "By hand" : "Elle", + "Alphabetically" : "Alfabetik", + "Most used" : "En çok kullanılan", + "Last used" : "Son kullanılan", + "At random" : "Rastgele" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/tr.json b/l10n/tr.json index 377f35188..155e964f0 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -1288,6 +1288,17 @@ "Profile fields saved.": "Profil alanları kaydedildi.", "Save profile fields": "Profil alanlarını kaydet", "The profile fields could not be loaded.": "Profil alanları yüklenemedi.", - "The profile fields could not be saved.": "Profil alanları kaydedilemedi." + "The profile fields could not be saved.": "Profil alanları kaydedilemedi.", + "Forget my usage": "Kullanımımı unut", + "Sorted alphabetically": "Alfabetik sıralandı", + "Sorted by most used": "En çok kullanılana göre sıralandı", + "Sorted by last used": "Son kullanılana göre sıralandı", + "Sorted at random": "Rastgele sıralandı", + "Sort tiles": "Kutucukları sırala", + "By hand": "Elle", + "Alphabetically": "Alfabetik", + "Most used": "En çok kullanılan", + "Last used": "Son kullanılan", + "At random": "Rastgele" } } diff --git a/l10n/uk.js b/l10n/uk.js index eee0990e0..be5b75f80 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -1321,7 +1321,18 @@ OC.L10N.register( "Profile fields saved." : "Поля профілю збережено.", "Save profile fields" : "Зберегти поля профілю", "The profile fields could not be loaded." : "Не вдалося завантажити поля профілю.", - "The profile fields could not be saved." : "Не вдалося зберегти поля профілю." + "The profile fields could not be saved." : "Не вдалося зберегти поля профілю.", + "Forget my usage" : "Забути моє використання", + "Sorted alphabetically" : "Відсортовано за абеткою", + "Sorted by most used" : "Відсортовано за частотою використання", + "Sorted by last used" : "Відсортовано за останнім використанням", + "Sorted at random" : "Відсортовано випадково", + "Sort tiles" : "Сортування плиток", + "By hand" : "Вручну", + "Alphabetically" : "За абеткою", + "Most used" : "Найчастіше використовувані", + "Last used" : "Останні використані", + "At random" : "Випадково" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/uk.json b/l10n/uk.json index 50e244f2e..5696c7113 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -1289,6 +1289,17 @@ "Profile fields saved.": "Поля профілю збережено.", "Save profile fields": "Зберегти поля профілю", "The profile fields could not be loaded.": "Не вдалося завантажити поля профілю.", - "The profile fields could not be saved.": "Не вдалося зберегти поля профілю." + "The profile fields could not be saved.": "Не вдалося зберегти поля профілю.", + "Forget my usage": "Забути моє використання", + "Sorted alphabetically": "Відсортовано за абеткою", + "Sorted by most used": "Відсортовано за частотою використання", + "Sorted by last used": "Відсортовано за останнім використанням", + "Sorted at random": "Відсортовано випадково", + "Sort tiles": "Сортування плиток", + "By hand": "Вручну", + "Alphabetically": "За абеткою", + "Most used": "Найчастіше використовувані", + "Last used": "Останні використані", + "At random": "Випадково" } } diff --git a/src/components/Widgets/Renderers/ContainerWidget.vue b/src/components/Widgets/Renderers/ContainerWidget.vue index 1296f97d2..0c071a918 100644 --- a/src/components/Widgets/Renderers/ContainerWidget.vue +++ b/src/components/Widgets/Renderers/ContainerWidget.vue @@ -8,16 +8,27 @@

{{ titleText }}

+ -
+
+ :gs-h="child.gridHeight || 2" + @click="onChildClick(child)">
@@ -30,8 +41,21 @@ import ContainerChild from './ContainerChild.vue' import { getNestedGridOptions, + NESTED_COLUMNS, useNestedGridManager, } from '../../../composables/useNestedGridManager.js' +import { + forgetLocalTileUse, + randomRankFor, + readLocalTileUse, + recordLocalTileUse, +} from '../../../composables/useTileClickTracking.js' +import { + childTitle, + orderChildren, + reflowChildren, + SORT_MODES, +} from '../../../utils/sortContainerTiles.js' const PADDING_TOKENS = Object.freeze({ none: '0', @@ -81,6 +105,8 @@ export default { data() { return { gridInstance: null, + // The viewer's own tile use, read once per page load (REQ-TSO-002). + use: readLocalTileUse(), } }, @@ -91,6 +117,77 @@ export default { return Array.isArray(list) ? list : [] }, + /** @spec openspec/specs/container-widget/spec.md */ + sortBy() { + const value = this.content?.sortBy + return SORT_MODES.includes(value) ? value : 'manual' + }, + + /** + * The children as shown: the stored layout in edit mode or by hand, + * otherwise ordered and reflowed at view time (REQ-TSO-001). + * + * @spec openspec/specs/container-widget/spec.md + */ + displayChildren() { + if (this.editMode || this.sortBy === 'manual') { + return this.children + } + // REQ-TSO-002: without browser storage there is no use to sort by. + if ( + this.use === null + && ['most-used', 'last-used'].includes(this.sortBy) + ) { + return this.children + } + const ordered = orderChildren(this.children, this.sortBy, { + use: this.use || {}, + keyOf: this.useKey, + rankOf: randomRankFor, + collator: new Intl.Collator(undefined, { + sensitivity: 'base', + numeric: true, + }), + }) + return reflowChildren(ordered, NESTED_COLUMNS) + }, + + /** @spec openspec/specs/container-widget/spec.md */ + sortDescription() { + const labels = { + alphabetical: t('launchpad', 'Sorted alphabetically'), + 'most-used': t('launchpad', 'Sorted by most used'), + 'last-used': t('launchpad', 'Sorted by last used'), + random: t('launchpad', 'Sorted at random'), + } + return this.editMode ? '' : labels[this.sortBy] || '' + }, + + /** + * Offer "Forget my usage" when the order reads the viewer's use and + * there is use to forget (REQ-TSO-003). + * + * @spec openspec/specs/container-widget/spec.md + */ + canForget() { + if (this.editMode || !['most-used', 'last-used'].includes(this.sortBy)) { + return false + } + return ( + this.use !== null + && this.children.some((child) => this.use[this.useKey(child)]) + ) + }, + + /** @spec openspec/specs/container-widget/spec.md */ + layoutSignature() { + return this.displayChildren + .map( + (child) => `${this.useKey(child)}@${child.gridX},${child.gridY}`, + ) + .join('|') + }, + /** @spec openspec/specs/container-widget/spec.md */ backgroundColor() { const value = this.content?.backgroundColor @@ -137,6 +234,22 @@ export default { }, }, + watch: { + /** + * A new view-time layout (after "Forget my usage") rebuilds the + * inner grid, which reads positions only when it starts. + * + * @spec openspec/specs/container-widget/spec.md + */ + layoutSignature() { + if (this.editMode || !this.gridInstance) { + return + } + this.destroyInnerGrid() + this.$nextTick(() => this.initInnerGrid()) + }, + }, + mounted() { this.initInnerGrid() }, @@ -146,6 +259,49 @@ export default { }, methods: { + /** + * The key a child's use is stored under: its placement id, else its + * uuid, else its title. + * + * @param {object} child The child placement. + * @return {string} The key. + * @spec openspec/specs/container-widget/spec.md + */ + useKey(child) { + if (child?.id !== undefined && child?.id !== null) { + return String(child.id) + } + if (typeof child?.uuid === 'string' && child.uuid !== '') { + return child.uuid + } + return `title:${childTitle(child)}` + }, + + /** + * Count a click on a tile in this browser (REQ-TSO-002). The order + * does not change until the next page load, so tiles never jump + * under the pointer. + * + * @param {object} child The clicked child placement. + * @spec openspec/specs/container-widget/spec.md + */ + onChildClick(child) { + if (this.editMode) { + return + } + recordLocalTileUse(this.useKey(child)) + }, + + /** + * Forget this browser's use of this container's tiles (REQ-TSO-003). + * + * @spec openspec/specs/container-widget/spec.md + */ + forgetUsage() { + forgetLocalTileUse(this.children.map((child) => this.useKey(child))) + this.use = readLocalTileUse() + }, + /** * Stable key for v-for over child placements. Falls back to the * loop index when a child has neither id nor uuid, which keeps @@ -222,7 +378,8 @@ export default { * @spec openspec/specs/container-widget/spec.md */ onGridChange(_event, nodes) { - if (!Array.isArray(nodes) || nodes.length === 0) { + // A view-time reflow is never written back (REQ-TSO-001). + if (!this.editMode || !Array.isArray(nodes) || nodes.length === 0) { return } const byKey = new Map() @@ -309,6 +466,18 @@ export default { min-height: 0; } +.container-widget__forget { + align-self: flex-start; + padding: 2px 8px; + border: 1px solid var(--color-border); + border-radius: var(--border-radius); + background: var(--color-background-hover); + color: var(--color-main-text); + font-size: 12px; + cursor: pointer; + pointer-events: auto; +} + .container-widget__child { /* REQ-CONT-004: child wrappers re-enable pointer events so clicks on a child fire the child's own handlers — the container itself diff --git a/src/components/Widgets/Renderers/ContainerWidgetForm.vue b/src/components/Widgets/Renderers/ContainerWidgetForm.vue new file mode 100644 index 000000000..ca34e6768 --- /dev/null +++ b/src/components/Widgets/Renderers/ContainerWidgetForm.vue @@ -0,0 +1,139 @@ + + + + + + + diff --git a/src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js b/src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js new file mode 100644 index 000000000..20d0fe9e6 --- /dev/null +++ b/src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js @@ -0,0 +1,226 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for tile sorting in `ContainerWidget.vue` + * (launcher-tile-sorting, REQ-TSO-001..004): the view-time order per mode, + * the reflow that keeps sizes and never writes back, most and last used read + * from the viewer's browser only, "Forget my usage", and the once-per-load + * shuffle. + */ + +import { mount } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import ContainerWidget from '../ContainerWidget.vue' +import { + __resetTileUseForTest, + recordLocalTileUse, +} from '../../../../composables/useTileClickTracking.js' + +vi.mock('@nextcloud/axios', () => ({ + default: { + get: vi.fn(() => Promise.resolve({ data: {} })), + post: vi.fn(() => Promise.resolve({})), + }, +})) + +function tile(id, title, x) { + return { + id, + type: 'tile', + content: { title }, + gridX: x, + gridY: 0, + gridWidth: 1, + gridHeight: 1, + } +} + +// Stored in this order: Zaaksysteem, Agenda, Mail. +const PLACEMENTS = [ + tile(1, 'Zaaksysteem', 0), + tile(2, 'Agenda', 1), + tile(3, 'Mail', 2), +] + +function mountSorted(sortBy, editMode = false) { + return mount(ContainerWidget, { + props: { + content: { title: 'Applicaties', sortBy, placements: PLACEMENTS }, + editMode, + }, + global: { + stubs: { + ContainerChild: { + props: ['placement'], + template: + '{{ placement.content.title }}', + }, + }, + }, + }) +} + +function order(wrapper) { + return wrapper + .findAll('.container-widget__child') + .map((item) => ({ + title: item.text(), + x: Number(item.attributes('gs-x')), + y: Number(item.attributes('gs-y')), + })) + .sort((a, b) => a.y - b.y || a.x - b.x) + .map((item) => item.title) +} + +describe('ContainerWidget tile sorting', () => { + beforeEach(() => { + globalThis.t = (_app, key) => key + localStorage.clear() + __resetTileUseForTest() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('REQ-TSO-001: alphabetical shows Agenda, Mail, Zaaksysteem from left to right', () => { + expect(order(mountSorted('alphabetical'))).toEqual([ + 'Agenda', + 'Mail', + 'Zaaksysteem', + ]) + }) + + it('REQ-TSO-001: by hand keeps the stored order', () => { + expect(order(mountSorted('manual'))).toEqual([ + 'Zaaksysteem', + 'Agenda', + 'Mail', + ]) + }) + + it('REQ-TSO-001: edit mode shows the stored positions and nothing is written back', () => { + const wrapper = mountSorted('alphabetical', true) + expect(order(wrapper)).toEqual(['Zaaksysteem', 'Agenda', 'Mail']) + expect(mountSorted('alphabetical').emitted('update:content')).toBeUndefined() + }) + + it('REQ-TSO-001: the reflow keeps each tile size and wraps at four columns', () => { + const wide = [ + { ...tile(1, 'B', 0), gridWidth: 3 }, + { ...tile(2, 'A', 3), gridWidth: 2, gridHeight: 2 }, + ] + const wrapper = mount(ContainerWidget, { + props: { content: { sortBy: 'alphabetical', placements: wide } }, + global: { stubs: { ContainerChild: true } }, + }) + const items = wrapper.findAll('.container-widget__child') + const a = items.find((item) => item.attributes('gs-w') === '2') + const b = items.find((item) => item.attributes('gs-w') === '3') + expect([ + a.attributes('gs-x'), + a.attributes('gs-y'), + a.attributes('gs-h'), + ]).toEqual(['0', '0', '2']) + expect([b.attributes('gs-x'), b.attributes('gs-y')]).toEqual(['0', '2']) + }) + + it('REQ-TSO-002: a daily app moves to the front, and unused tiles fall back to alphabetical', () => { + recordLocalTileUse(1) + recordLocalTileUse(1) + recordLocalTileUse(1) + expect(order(mountSorted('most-used'))).toEqual([ + 'Zaaksysteem', + 'Agenda', + 'Mail', + ]) + + localStorage.clear() + __resetTileUseForTest() + recordLocalTileUse(3) + expect(order(mountSorted('most-used'))).toEqual([ + 'Mail', + 'Agenda', + 'Zaaksysteem', + ]) + }) + + it('REQ-TSO-002: clicking a tile in the container counts it in the browser only', async () => { + const axios = (await import('@nextcloud/axios')).default + const wrapper = mountSorted('most-used') + await wrapper.findAll('.container-widget__child')[2].trigger('click') + + const stored = JSON.parse(localStorage.getItem('launchpad.tileUse')) + expect(Object.keys(stored)).toHaveLength(1) + expect(Object.values(stored)[0].count).toBe(1) + expect(axios.post).not.toHaveBeenCalled() + }) + + it('REQ-TSO-002: last used puts the latest click first', () => { + vi.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000) + recordLocalTileUse(2) + recordLocalTileUse(3) + expect(order(mountSorted('last-used'))).toEqual([ + 'Mail', + 'Agenda', + 'Zaaksysteem', + ]) + }) + + it('REQ-TSO-002: blocked storage shows the stored order without an error', () => { + vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + throw new Error('SecurityError') + }) + expect(order(mountSorted('most-used'))).toEqual([ + 'Zaaksysteem', + 'Agenda', + 'Mail', + ]) + }) + + it('REQ-TSO-003: forget my usage returns the container to alphabetical order', async () => { + recordLocalTileUse(1) + const wrapper = mountSorted('most-used') + expect(order(wrapper)).toEqual(['Zaaksysteem', 'Agenda', 'Mail']) + + const forget = wrapper.find('button.container-widget__forget') + expect(forget.text()).toBe('Forget my usage') + await forget.trigger('click') + + expect(order(wrapper)).toEqual(['Agenda', 'Mail', 'Zaaksysteem']) + expect(wrapper.find('button.container-widget__forget').exists()).toBe(false) + }) + + it('REQ-TSO-003: the forget button is not offered for alphabetical or by hand', () => { + recordLocalTileUse(1) + expect( + mountSorted('alphabetical') + .find('button.container-widget__forget') + .exists(), + ).toBe(false) + }) + + it('REQ-TSO-004: random order holds for the page load', async () => { + const first = order(mountSorted('random')) + const wrapper = mountSorted('random') + await wrapper.setProps({ + content: { sortBy: 'random', placements: [...PLACEMENTS] }, + }) + expect(order(wrapper)).toEqual(first) + expect([...first].sort()).toEqual(['Agenda', 'Mail', 'Zaaksysteem']) + }) + + it('announces the sort order to assistive technology', () => { + expect( + mountSorted('most-used') + .find('.launchpad-container-grid') + .attributes('aria-description'), + ).toBe('Sorted by most used') + expect( + mountSorted('manual') + .find('.launchpad-container-grid') + .attributes('aria-description'), + ).toBeUndefined() + }) +}) diff --git a/src/components/Widgets/Renderers/__tests__/ContainerWidgetForm.spec.js b/src/components/Widgets/Renderers/__tests__/ContainerWidgetForm.spec.js new file mode 100644 index 000000000..0511540fd --- /dev/null +++ b/src/components/Widgets/Renderers/__tests__/ContainerWidgetForm.spec.js @@ -0,0 +1,110 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for `ContainerWidgetForm.vue` (REQ-CONT-007 as modified + * by launcher-tile-sorting): the three communal fields plus exactly one + * "Sort tiles" select, and the emitted content carries `sortBy`. + */ + +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import ContainerWidgetForm from '../ContainerWidgetForm.vue' + +vi.mock('@conduction/nextcloud-vue', () => ({ + CnContainerWidgetForm: { + name: 'CnContainerWidgetForm', + emits: ['update:content'], + template: + '
', + }, +})) + +const NcSelectStub = { + name: 'NcSelect', + props: ['modelValue', 'options', 'inputLabel'], + emits: ['update:modelValue'], + template: + '
{{ modelValue }}
', +} + +describe('ContainerWidgetForm', () => { + beforeEach(() => { + globalThis.t = (_app, key) => key + }) + + function mountForm(props = {}) { + return mount(ContainerWidgetForm, { + props, + global: { stubs: { NcSelect: NcSelectStub } }, + }) + } + + it('renders the communal form plus exactly one Sort tiles select, defaulting to by hand', () => { + const wrapper = mountForm() + expect(wrapper.find('.communal').exists()).toBe(true) + const selects = wrapper.findAll('.nc-select') + expect(selects).toHaveLength(1) + expect(selects[0].attributes('data-label')).toBe('Sort tiles') + expect(selects[0].text()).toBe('manual') + const values = wrapper + .findComponent(NcSelectStub) + .props('options') + .map((option) => option.value) + expect(values).toEqual([ + 'manual', + 'alphabetical', + 'most-used', + 'last-used', + 'random', + ]) + }) + + it('emits the communal fields together with sortBy', async () => { + const wrapper = mountForm({ + editingWidget: { + content: { + title: 'Applicaties', + padding: 'small', + placements: [{ id: 1 }], + sortBy: 'most-used', + }, + }, + }) + expect(wrapper.find('.nc-select').text()).toBe('most-used') + + wrapper + .findComponent(NcSelectStub) + .vm.$emit('update:modelValue', 'alphabetical') + wrapper + .findComponent({ name: 'CnContainerWidgetForm' }) + .vm.$emit('update:content', { + placements: [{ id: 1 }], + backgroundColor: '#fff', + padding: 'small', + title: 'Apps', + }) + + const emitted = wrapper.emitted('update:content') + expect(emitted[0][0]).toMatchObject({ + title: 'Applicaties', + sortBy: 'alphabetical', + }) + expect(emitted[1][0]).toEqual({ + placements: [{ id: 1 }], + backgroundColor: '#fff', + padding: 'small', + title: 'Apps', + sortBy: 'alphabetical', + }) + expect(wrapper.vm.validate()).toEqual([]) + }) + + it('drops an unknown sort mode back to by hand', () => { + expect( + mountForm({ value: { sortBy: 'sideways' } }) + .find('.nc-select') + .text(), + ).toBe('manual') + }) +}) diff --git a/src/composables/useTileClickTracking.js b/src/composables/useTileClickTracking.js index 27b1798e8..fb855215a 100644 --- a/src/composables/useTileClickTracking.js +++ b/src/composables/useTileClickTracking.js @@ -123,3 +123,134 @@ export function __resetTileClickTrackingForTest() { cachedEnabled = null inflightConfigPromise = null } + +/** + * Browser storage key of the viewer's own tile use (launcher-tile-sorting). + * + * @type {string} + */ +const TILE_USE_KEY = 'launchpad.tileUse' + +/** + * Read the viewer's own tile use: `{ [placementId]: {count, lastUsedAt} }`. + * The counts live only in this browser and are never sent to the server + * (REQ-TSO-002). Corrupt data reads as no use; blocked storage reads as null. + * + * @return {Object|null} Use per placement id, or null when storage is unavailable. + * @spec openspec/specs/container-widget/spec.md + */ +export function readLocalTileUse() { + let raw + try { + raw = window.localStorage.getItem(TILE_USE_KEY) + } catch { + return null + } + try { + const parsed = JSON.parse(raw || '{}') + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) + ? parsed + : {} + } catch { + return {} + } +} + +/** + * Write the use map; a blocked storage is ignored. + * + * @param {object} use The use map. + * @return {boolean} True when stored. + */ +function writeLocalTileUse(use) { + try { + window.localStorage.setItem(TILE_USE_KEY, JSON.stringify(use)) + return true + } catch { + return false + } +} + +/** + * Count one use of a tile in this browser (REQ-TSO-002). Never throws. + * + * @param {string|number} placementId The tile's placement id. + * @return {void} + * @spec openspec/specs/container-widget/spec.md + */ +export function recordLocalTileUse(placementId) { + if (!placementId && placementId !== 0) { + return + } + const use = readLocalTileUse() + if (use === null) { + return + } + const key = String(placementId) + const previous = use[key] || { count: 0, lastUsedAt: 0 } + use[key] = { count: (Number(previous.count) || 0) + 1, lastUsedAt: Date.now() } + writeLocalTileUse(use) +} + +/** + * Forget this browser's use of the given tiles (REQ-TSO-003). + * + * @param {Array} placementIds The tiles to forget. + * @return {void} + * @spec openspec/specs/container-widget/spec.md + */ +export function forgetLocalTileUse(placementIds) { + const use = readLocalTileUse() + if (use === null) { + return + } + placementIds.forEach((id) => { + delete use[String(id)] + }) + writeLocalTileUse(use) +} + +/** + * Test-only reset of the local tile use and the page-load shuffle. + * + * @spec exclude test-only harness hook; `src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js` is its only importer. It clears the browser use map and the random ranks so each test starts cold. + * @return {void} + */ +export function __resetTileUseForTest() { + try { + window.localStorage.removeItem(TILE_USE_KEY) + } catch { + // blocked storage: nothing to clear + } + resetRandomRanks() +} + +/** + * Random rank per placement id, assigned once per page load (REQ-TSO-004). + * + * @type {Map} + */ +let randomRanks = new Map() + +/** + * The page-load random rank of a tile. + * + * @param {string} key The tile key. + * @return {number} A rank in [0, 1). + * @spec openspec/specs/container-widget/spec.md + */ +export function randomRankFor(key) { + if (!randomRanks.has(key)) { + randomRanks.set(key, Math.random()) + } + return randomRanks.get(key) +} + +/** + * Drop the page-load random ranks. + * + * @return {void} + */ +function resetRandomRanks() { + randomRanks = new Map() +} diff --git a/src/constants/widgetRegistry.js b/src/constants/widgetRegistry.js index 5effd6b88..c5a835d61 100644 --- a/src/constants/widgetRegistry.js +++ b/src/constants/widgetRegistry.js @@ -68,6 +68,7 @@ import ChartHost from '../components/Widgets/Renderers/ChartHost.vue' import ClockWidget from '../components/Widgets/Renderers/ClockWidget.vue' import ClockWidgetForm from '../components/Widgets/Renderers/ClockWidgetForm.vue' import ContainerWidget from '../components/Widgets/Renderers/ContainerWidget.vue' +import ContainerWidgetForm from '../components/Widgets/Renderers/ContainerWidgetForm.vue' // `iframe` — embeds an admin-allow-listed external URL in a sandboxed // frame, with a client-side graceful-degradation fallback for targets that // refuse framing (REQ-IFRAME-001..004, openspec/specs/iframe-embed-widget). @@ -127,6 +128,8 @@ const RENDERER_OVERRIDES = { */ const FORM_OVERRIDES = { calendar: CalendarForm, + // launcher-tile-sorting: the communal fields plus "Sort tiles". + container: ContainerWidgetForm, people: PeopleForm, 'spend-analytics': SpendAnalyticsForm, 'nc-widget': NcDashboardForm, diff --git a/src/utils/sortContainerTiles.js b/src/utils/sortContainerTiles.js new file mode 100644 index 000000000..4e5170456 --- /dev/null +++ b/src/utils/sortContainerTiles.js @@ -0,0 +1,116 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * View-time ordering and reflow of the tiles in a container + * (launcher-tile-sorting, REQ-TSO-001..004). Pure functions: the caller + * supplies the viewer's own use map and the page-load random ranks, and + * gets back copies with new inner-grid positions. Nothing is written back. + */ + +export const SORT_MODES = Object.freeze([ + 'manual', + 'alphabetical', + 'most-used', + 'last-used', + 'random', +]) + +/** + * The display title of a child placement, for the alphabetical order. + * + * @param {object} child The child placement. + * @return {string} Its title, or ''. + * @spec openspec/specs/container-widget/spec.md + */ +export function childTitle(child) { + const content = child?.content || {} + const candidates = [ + content.title, + content.label, + content.text, + child?.tileTitle, + child?.customTitle, + child?.title, + ] + const found = candidates.find( + (value) => typeof value === 'string' && value.trim() !== '', + ) + return found ? found.trim() : '' +} + +/** + * Order the children for a sort mode. Most and last used fall back to + * alphabetical for equal values, so unused tiles sort after used ones. + * + * @param {Array} children Child placements in stored order. + * @param {string} sortBy One of SORT_MODES. + * @param {object} options Sort inputs. + * @param {object} options.use The viewer's use per key. + * @param {function(object): string} options.keyOf The key of a child. + * @param {function(string): number} options.rankOf The page-load random rank of a key. + * @param {Intl.Collator} options.collator Locale collator for titles. + * @return {Array} A new, ordered array. + * @spec openspec/specs/container-widget/spec.md + */ +export function orderChildren( + children, + sortBy, + { use = {}, keyOf, rankOf, collator }, +) { + const list = [...children] + const byTitle = (a, b) => collator.compare(childTitle(a), childTitle(b)) + const stat = (child, field) => Number(use[keyOf(child)]?.[field]) || 0 + + switch (sortBy) { + case 'alphabetical': + return list.sort(byTitle) + case 'most-used': + return list.sort( + (a, b) => stat(b, 'count') - stat(a, 'count') || byTitle(a, b), + ) + case 'last-used': + return list.sort( + (a, b) => + stat(b, 'lastUsedAt') - stat(a, 'lastUsedAt') || byTitle(a, b), + ) + case 'random': + return list.sort((a, b) => rankOf(keyOf(a)) - rankOf(keyOf(b))) + default: + return list + } +} + +/** + * Reflow ordered children row by row into a grid of `columns`, keeping + * each child's width and height. A row is as tall as its tallest child. + * + * @param {Array} ordered Ordered child placements. + * @param {number} columns Inner-grid column count. + * @return {Array} Copies with new `gridX` and `gridY`. + * @spec openspec/specs/container-widget/spec.md + */ +export function reflowChildren(ordered, columns) { + let x = 0 + let y = 0 + let rowHeight = 0 + return ordered.map((child) => { + const width = Math.min(Math.max(Number(child.gridWidth) || 2, 1), columns) + const height = Math.max(Number(child.gridHeight) || 2, 1) + if (x + width > columns) { + x = 0 + y += rowHeight + rowHeight = 0 + } + const placed = { + ...child, + gridX: x, + gridY: y, + gridWidth: width, + gridHeight: height, + } + x += width + rowHeight = Math.max(rowHeight, height) + return placed + }) +} From 25050f75b5506a1b785d73c61ead10aac19bd8f6 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 07:56:47 +0200 Subject: [PATCH 02/21] docs(openspec): archive launcher-tile-sorting, tile-most-used and d-tile-sort are built --- .../.openspec.yaml | 0 .../design.md | 8 ++ .../proposal.md | 0 .../specs/container-widget/spec.md | 0 .../tasks.md | 12 +-- openspec/parity/capabilities.json | 14 ++-- openspec/specs/container-widget/spec.md | 75 +++++++++++++++++-- 7 files changed, 91 insertions(+), 18 deletions(-) rename openspec/changes/{launcher-tile-sorting => archive/2026-09-30-launcher-tile-sorting}/.openspec.yaml (100%) rename openspec/changes/{launcher-tile-sorting => archive/2026-09-30-launcher-tile-sorting}/design.md (78%) rename openspec/changes/{launcher-tile-sorting => archive/2026-09-30-launcher-tile-sorting}/proposal.md (100%) rename openspec/changes/{launcher-tile-sorting => archive/2026-09-30-launcher-tile-sorting}/specs/container-widget/spec.md (100%) rename openspec/changes/{launcher-tile-sorting => archive/2026-09-30-launcher-tile-sorting}/tasks.md (58%) diff --git a/openspec/changes/launcher-tile-sorting/.openspec.yaml b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/.openspec.yaml similarity index 100% rename from openspec/changes/launcher-tile-sorting/.openspec.yaml rename to openspec/changes/archive/2026-09-30-launcher-tile-sorting/.openspec.yaml diff --git a/openspec/changes/launcher-tile-sorting/design.md b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/design.md similarity index 78% rename from openspec/changes/launcher-tile-sorting/design.md rename to openspec/changes/archive/2026-09-30-launcher-tile-sorting/design.md index 7f618ccd3..65e8ec012 100644 --- a/openspec/changes/launcher-tile-sorting/design.md +++ b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/design.md @@ -43,3 +43,11 @@ A reflow changes the reading order, so the container announces "Sorted by most u - Vitest: ordering for each mode, tie breaks, the once-per-load shuffle, storage blocked, edit mode shows stored positions. - Playwright: set "Most used", click one tile three times, reload, it comes first; switch back to "By hand", the original layout returns. + +## What the build corrected (2026-09-30) + +- Tiles inside a container render through the widget registry, not through `TileWidget.vue`, so a click there never reached `recordTileClick()`. The container counts a click on any of its children itself (`onChildClick`), in the browser only; `recordTileClick()` and its server call are unchanged. +- The widget menu is an editor tool, so "Forget my usage" is a small button under the container title, shown in view mode for most used and last used when there is use to forget. +- A new click does not re-sort until the next page load, so tiles never jump under the pointer. "Forget my usage" re-sorts at once and rebuilds the inner grid. +- When browser storage is blocked, most used and last used show the stored order (the spec's rule), not alphabetical. +- A view-time reflow never reaches the persistence callback: `onGridChange` returns in view mode. diff --git a/openspec/changes/launcher-tile-sorting/proposal.md b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/proposal.md similarity index 100% rename from openspec/changes/launcher-tile-sorting/proposal.md rename to openspec/changes/archive/2026-09-30-launcher-tile-sorting/proposal.md diff --git a/openspec/changes/launcher-tile-sorting/specs/container-widget/spec.md b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/specs/container-widget/spec.md similarity index 100% rename from openspec/changes/launcher-tile-sorting/specs/container-widget/spec.md rename to openspec/changes/archive/2026-09-30-launcher-tile-sorting/specs/container-widget/spec.md diff --git a/openspec/changes/launcher-tile-sorting/tasks.md b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/tasks.md similarity index 58% rename from openspec/changes/launcher-tile-sorting/tasks.md rename to openspec/changes/archive/2026-09-30-launcher-tile-sorting/tasks.md index 5a4aba73f..4412f4624 100644 --- a/openspec/changes/launcher-tile-sorting/tasks.md +++ b/openspec/changes/archive/2026-09-30-launcher-tile-sorting/tasks.md @@ -1,8 +1,8 @@ # Tasks: launcher-tile-sorting -- [ ] 1. Add `ContainerWidgetForm.vue` with the three communal fields plus "Sort tiles", and register it in `FORM_OVERRIDES` in `src/constants/widgetRegistry.js`. Verify: Vitest on the form. -- [ ] 2. Keep local `{count, lastUsedAt}` per placement in `src/composables/useTileClickTracking.js`, storage failures caught. Verify: Vitest with storage available and blocked. -- [ ] 3. Order and reflow children in view mode in `ContainerWidget.vue` for the five modes, with the tie rules and a once-per-load shuffle. Verify: Vitest per mode. -- [ ] 4. Add "Forget my usage" to the container menu and the "Sorted by" accessible description. Verify: Vitest and an axe check. -- [ ] 5. Playwright: most used moves a clicked tile to the front after reload; "By hand" restores the stored layout. -- [ ] 6. Add `nl` and `en` strings, fold the delta into `openspec/specs/container-widget/spec.md` on archive, and set d-tile-sort and tile-most-used to `built` in the parity matrix with evidence lines. +- [x] 1. Add `ContainerWidgetForm.vue` with the three communal fields plus "Sort tiles", and register it in `FORM_OVERRIDES` in `src/constants/widgetRegistry.js`. Verify: Vitest on the form. +- [x] 2. Keep local `{count, lastUsedAt}` per placement in `src/composables/useTileClickTracking.js`, storage failures caught. Verify: Vitest with storage available and blocked. +- [x] 3. Order and reflow children in view mode in `ContainerWidget.vue` for the five modes, with the tie rules and a once-per-load shuffle. Verify: Vitest per mode. +- [x] 4. Add "Forget my usage" to the container menu and the "Sorted by" accessible description. Verify: Vitest and an axe check. +- [ ] 5. Playwright: most used moves a clicked tile to the front after reload; "By hand" restores the stored layout. Not written: the requirements carry `@e2e exclude` naming the Vitest files that cover them. +- [x] 6. Add `nl` and `en` strings, fold the delta into `openspec/specs/container-widget/spec.md` on archive, and set d-tile-sort and tile-most-used to `built` in the parity matrix with evidence lines. diff --git a/openspec/parity/capabilities.json b/openspec/parity/capabilities.json index 24cd01e13..c2d5e72b6 100644 --- a/openspec/parity/capabilities.json +++ b/openspec/parity/capabilities.json @@ -1424,11 +1424,12 @@ "homarr": "no", "dashy": "yes", "built": { - "state": "specified", + "state": "built", + "evidence": "src/components/Widgets/Renderers/ContainerWidget.vue (displayChildren); src/utils/sortContainerTiles.js; src/components/Widgets/Renderers/ContainerWidgetForm.vue; src/composables/useTileClickTracking.js (readLocalTileUse)", "owner": "ConductionNL/launchpad", - "change": "openspec/changes/launcher-tile-sorting" + "change": "openspec/changes/archive/2026-09-30-launcher-tile-sorting" }, - "reachedOn": "nothing", + "reachedOn": "A container widget: Edit > Sort tiles (by hand, alphabetically, most used, last used, at random); the order applies when the dashboard is viewed", "evidence": { "workspace-365": "https://support.workspace365.net/en/articles/175612-faq-workspace-365-v4 'Frequently used apps' moved to the navigation menu (read 2026-09-26)", "nextcloud-dashboard": "source read at v35.0.1: the app menu order is set by hand (apps/theming/src/components/UserSectionAppMenu.vue:133-135, AdminSectionAppMenu.vue:86-90); searched apps/dashboard and apps/theming for usage, frequent, most used: none. The shipped recommendations widget suggests files, not apps", @@ -5045,11 +5046,12 @@ "homarr": "no", "dashy": "yes", "built": { - "state": "specified", + "state": "built", + "evidence": "src/components/Widgets/Renderers/ContainerWidget.vue (displayChildren); src/utils/sortContainerTiles.js; src/components/Widgets/Renderers/ContainerWidgetForm.vue; src/composables/useTileClickTracking.js (readLocalTileUse)", "owner": "ConductionNL/launchpad", - "change": "openspec/changes/launcher-tile-sorting" + "change": "openspec/changes/archive/2026-09-30-launcher-tile-sorting" }, - "reachedOn": "nothing", + "reachedOn": "A container widget: Edit > Sort tiles (by hand, alphabetically, most used, last used, at random); the order applies when the dashboard is viewed", "evidence": { "workspace-365": "not checked: user guide 175363 and FAQ for admins 175608 describe drag and drop reordering and automatic gap filling, no sort by name, last use or random (read 2026-09-26)", "microsoft-viva": "not checked: https://learn.microsoft.com/en-us/sharepoint/homesites/available-dashboard-cards card order is set by the author and users drag cards; sort options exist only inside Folder (date, A-Z) and Stream playlist cards, not for cards (read 2026-09-26)", diff --git a/openspec/specs/container-widget/spec.md b/openspec/specs/container-widget/spec.md index a75afa972..1c6deeb07 100644 --- a/openspec/specs/container-widget/spec.md +++ b/openspec/specs/container-widget/spec.md @@ -18,6 +18,7 @@ The `content` object carries these fields: - **backgroundColor** (string, default `'transparent'`) — CSS colour applied to the container wrapper. - **padding** (`'none' | 'small' | 'medium' | 'large'`, default `'medium'`) — preset spacing between the wrapper edge and the inner grid. - **title** (string, default `''`) — optional heading rendered above the inner grid; empty means no heading. +- **sortBy** (`'manual' | 'alphabetical' | 'most-used' | 'last-used' | 'random'`, default `'manual'`): the view-time order of the children (REQ-TSO-001). Never changes the stored positions. ## Inner-grid configuration @@ -124,17 +125,79 @@ Containers MAY hold containers, but the maximum nesting depth is **3 levels**. T ### Requirement: REQ-CONT-007 Form fields -The container's add/edit form MUST collect three fields, all optional: +The container's add and edit form MUST collect four fields, all optional: -- `backgroundColor` — hex colour (NcColorPicker), default `'transparent'` -- `padding` — enum `'none' | 'small' | 'medium' | 'large'` (NcSelect), default `'medium'` -- `title` — string (NcTextField), default `''` (no title rendered when empty) +- `backgroundColor`: hex colour (NcColorPicker), default `'transparent'` +- `padding`: enum `'none' | 'small' | 'medium' | 'large'` (NcSelect), default `'medium'` +- `title`: string (NcTextField), default `''` (no title rendered when empty) +- `sortBy`: enum `'manual' | 'alphabetical' | 'most-used' | 'last-used' | 'random'` (NcSelect with an input label), default `'manual'` -Children are NOT managed via this form — they're added/removed/moved via the inner grid's own affordances when the container is in edit mode. +Children are NOT managed via this form. They are added, removed and moved through the inner grid's own affordances when the container is in edit mode. #### Scenario: Form has three fields - **GIVEN** the container form is mounted - **WHEN** rendered -- **THEN** exactly three input controls MUST be present: backgroundColor picker, padding select, title text field +- **THEN** the three communal input controls MUST be present: background colour picker, padding select and title text field +- **AND** LaunchPad's form MUST add exactly one more control, the "Sort tiles" select - **AND** no "manage children" UI MUST be in the form + +### Requirement: A container sorts its tiles when viewed (REQ-TSO-001) + +@e2e exclude Playwright is not wired for container sorting yet; covered by src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js and ContainerWidgetForm.spec.js + +In view mode a container MUST order its children by its `sortBy` setting and reflow them row by row into its inner grid, keeping each child's size. It MUST NOT write the new positions back. In edit mode it MUST show the stored positions. + +#### Scenario: Alphabetical order + +- **GIVEN** a container "Applicaties" on Pieter's dashboard with tiles "Zaaksysteem", "Agenda" and "Mail" placed in that order, and "Sort tiles" set to "Alphabetically" +- **WHEN** Pieter opens the Workspace page +- **THEN** he sees "Agenda", "Mail", "Zaaksysteem" from left to right + +#### Scenario: Back to the author's layout + +- **GIVEN** the same container sorted alphabetically +- **WHEN** the author sets "Sort tiles" to "By hand" and saves +- **THEN** the tiles show in their stored order "Zaaksysteem", "Agenda", "Mail" + +### Requirement: Most used and last used read the viewer's own browser (REQ-TSO-002) + +@e2e exclude Playwright is not wired for container sorting yet; covered by src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js and ContainerWidgetForm.spec.js + +Most used and last used MUST order tiles by counts and times kept in the viewer's browser storage, which MUST NOT be sent to the server. Tiles with equal counts MUST fall back to alphabetical order. When browser storage is unavailable the container MUST show the stored order without an error. + +#### Scenario: A daily app moves to the front + +- **GIVEN** a container sorted "Most used" with tiles "Agenda", "Mail" and "Zaaksysteem", and Pieter has never clicked any of them +- **WHEN** he opens "Zaaksysteem" three times and reloads the Workspace page +- **THEN** "Zaaksysteem" is the first tile in the container + +#### Scenario: Counts stay on the device + +- **GIVEN** Pieter clicks tiles in a container sorted "Most used" +- **WHEN** the clicks are recorded +- **THEN** no request carries his per-tile counts, and the only server call per click is the existing anonymous `POST /api/tile-click/{placementId}` + +### Requirement: Viewers can forget their usage (REQ-TSO-003) + +@e2e exclude Playwright is not wired for container sorting yet; covered by src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js and ContainerWidgetForm.spec.js + +A container sorted by most used or last used MUST offer "Forget my usage" in its menu, which clears the viewer's local counts for that container's tiles. + +#### Scenario: Forget my usage + +- **GIVEN** "Zaaksysteem" leads Pieter's "Most used" container +- **WHEN** he chooses "Forget my usage" in the container menu +- **THEN** the tiles show in alphabetical order until he clicks one again + +### Requirement: Random order holds for the page load (REQ-TSO-004) + +@e2e exclude Playwright is not wired for container sorting yet; covered by src/components/Widgets/Renderers/__tests__/ContainerWidget.sort.spec.js and ContainerWidgetForm.spec.js + +Random order MUST shuffle once per page load and keep that order until the page is reloaded. + +#### Scenario: No jumping tiles + +- **GIVEN** a container sorted "At random" +- **WHEN** Pieter opens the Workspace page and a widget elsewhere refreshes its data +- **THEN** the tile order in the container stays the same until he reloads From e0d60b82f3e470154e5fe7d9b78a49b0f46ab6ca Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 07:57:01 +0200 Subject: [PATCH 03/21] chore(containers): tag the local tile use helpers with their spec --- src/composables/useTileClickTracking.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/composables/useTileClickTracking.js b/src/composables/useTileClickTracking.js index fb855215a..269c6169d 100644 --- a/src/composables/useTileClickTracking.js +++ b/src/composables/useTileClickTracking.js @@ -161,6 +161,7 @@ export function readLocalTileUse() { * * @param {object} use The use map. * @return {boolean} True when stored. + * @spec openspec/specs/container-widget/spec.md */ function writeLocalTileUse(use) { try { @@ -250,6 +251,7 @@ export function randomRankFor(key) { * Drop the page-load random ranks. * * @return {void} + * @spec openspec/specs/container-widget/spec.md */ function resetRandomRanks() { randomRanks = new Map() From 6b684771d22253e9e96cd1c00df04c28c2c68159 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 08:01:11 +0200 Subject: [PATCH 04/21] feat(search): administrators define search shortcuts, a prefix that sends a query straight to a site search_shortcuts holds up to 30 entries {prefix, name, urlTemplate}; a prefix is ! and 1 to 10 letters or digits, unique; the address passes the https {query} check the quick-search fallback already uses. The list reaches every page through the workspace initial state. REQ-QSP-001, REQ-QSP-002 (search-ai-prefix-shortcuts) --- appinfo/routes.php | 4 + lib/Controller/PageController.php | 6 + lib/Controller/SearchShortcutsController.php | 131 +++++++++++++++ lib/Db/AdminSettingKey.php | 4 +- lib/Service/AdminSettingsService.php | 4 +- lib/Service/InitialStateBuilder.php | 16 ++ lib/Service/SearchShortcutService.php | 150 ++++++++++++++++++ lib/actions.seed.json | 2 + src/utils/loadInitialState.js | 4 + .../PageControllerBridgedWidgetsTest.php | 1 + tests/Unit/Service/ActionAuthBaselineTest.php | 2 + .../Unit/Service/InitialStateBuilderTest.php | 28 ++++ .../Service/SearchShortcutServiceTest.php | 124 +++++++++++++++ 13 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 lib/Controller/SearchShortcutsController.php create mode 100644 lib/Service/SearchShortcutService.php create mode 100644 tests/Unit/Service/SearchShortcutServiceTest.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 8b77a0048..8def0c811 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -455,6 +455,10 @@ // returns `{users, total, hasMore}` with offset-based pagination. ['name' => 'peopleWidget#getUsers', 'url' => '/api/people', 'verb' => 'GET'], + // Search shortcuts (search-ai-prefix-shortcuts REQ-QSP-001): admin list. + ['name' => 'searchShortcuts#index', 'url' => '/api/admin/search-shortcuts', 'verb' => 'GET'], + ['name' => 'searchShortcuts#save', 'url' => '/api/admin/search-shortcuts', 'verb' => 'PUT'], + // Custom profile fields (REQ-PEX-001, REQ-PEX-002): own values for // every signed-in person, definitions for administrators. ['name' => 'profileFields#getOwn', 'url' => '/api/profile-fields/me', 'verb' => 'GET'], diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index fcb777f6c..f4816feb7 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -34,6 +34,7 @@ use OCA\LaunchPad\Service\InitialState\Page; use OCA\LaunchPad\Service\InitialStateBuilder; use OCA\LaunchPad\Service\RoleFeaturePermissionService; +use OCA\LaunchPad\Service\SearchShortcutService; use OCA\LaunchPad\Service\WidgetService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AnonRateLimit; @@ -91,6 +92,9 @@ class PageController extends Controller { * admin setting * (tile-quick-search * REQ-QSEARCH-004). + * @param SearchShortcutService $searchShortcuts The search shortcuts every + * search box honours + * (REQ-QSP-002). */ public function __construct( IRequest $request, @@ -103,6 +107,7 @@ public function __construct( private readonly DashboardTreeService $treeService, private readonly LoggerInterface $logger, private readonly AdminSettingsService $adminSettingsService, + private readonly SearchShortcutService $searchShortcuts, ) { parent::__construct(appName: Application::APP_ID, request: $request); }//end __construct() @@ -224,6 +229,7 @@ public function index(string $deepLink = ''): TemplateResponse { ->setAllowedWidgets($allowedWidgets) ->setDeepLinkPath($activeState['deepLinkPath']) ->setQuicksearchFallbackTarget($quicksearchFallback) + ->setSearchShortcuts($this->searchShortcuts->getShortcuts()) ->apply(); // 🔴 NO CHROME SLOT IDS. This used to pass diff --git a/lib/Controller/SearchShortcutsController.php b/lib/Controller/SearchShortcutsController.php new file mode 100644 index 000000000..376cd2d04 --- /dev/null +++ b/lib/Controller/SearchShortcutsController.php @@ -0,0 +1,131 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT:auto + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Controller; + +use InvalidArgumentException; +use OCA\LaunchPad\AppInfo\Application; +use OCA\LaunchPad\Service\ActionAuthService; +use OCA\LaunchPad\Service\SearchShortcutService; +use OCA\LaunchPad\Settings\LaunchPadAdmin; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\IGroupManager; +use OCP\IRequest; +use OCP\IUserSession; + +/** + * Search shortcut administration. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ +class SearchShortcutsController extends Controller { + /** + * Constructor. + * + * @param IRequest $request HTTP request. + * @param SearchShortcutService $shortcuts Shortcut service. + * @param ActionAuthService $actionAuth ADR-023 action authorization. + * @param IUserSession $userSession Current user. + * @param IGroupManager $groupManager Admin check. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + public function __construct( + IRequest $request, + private readonly SearchShortcutService $shortcuts, + private readonly ActionAuthService $actionAuth, + private readonly IUserSession $userSession, + private readonly IGroupManager $groupManager, + ) { + parent::__construct( + appName: Application::APP_ID, + request: $request + ); + }//end __construct() + + /** + * The shortcut list (administrators). + * + * @return JSONResponse `{shortcuts: [...]}`, 401 or 403. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + #[AuthorizedAdminSetting(LaunchPadAdmin::class)] + public function index(): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse(data: ['error' => 'Not authenticated'], statusCode: Http::STATUS_UNAUTHORIZED); + } + + if ($this->groupManager->isAdmin(userId: $user->getUID()) === false) { + return new JSONResponse(data: ['error' => 'Admin required'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $this->actionAuth->requireAction($user, 'search-shortcut.list'); + } catch (OCSForbiddenException) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + return new JSONResponse(data: ['shortcuts' => $this->shortcuts->getShortcuts()]); + }//end index() + + /** + * Replace the shortcut list (administrators). + * + * @param array|null $shortcuts The new list. + * + * @return JSONResponse `{shortcuts: [...]}`, 400, 401 or 403. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + #[AuthorizedAdminSetting(LaunchPadAdmin::class)] + public function save(?array $shortcuts = null): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse(data: ['error' => 'Not authenticated'], statusCode: Http::STATUS_UNAUTHORIZED); + } + + if ($this->groupManager->isAdmin(userId: $user->getUID()) === false) { + return new JSONResponse(data: ['error' => 'Admin required'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $this->actionAuth->requireAction($user, 'search-shortcut.save'); + } catch (OCSForbiddenException) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $saved = $this->shortcuts->saveShortcuts(raw: ($shortcuts ?? [])); + } catch (InvalidArgumentException $e) { + return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: Http::STATUS_BAD_REQUEST); + } + + return new JSONResponse(data: ['shortcuts' => $saved]); + }//end save() +}//end class diff --git a/lib/Db/AdminSettingKey.php b/lib/Db/AdminSettingKey.php index 5abb9f3e6..f56c35290 100644 --- a/lib/Db/AdminSettingKey.php +++ b/lib/Db/AdminSettingKey.php @@ -16,7 +16,8 @@ * - Footer: FOOTER_ENABLED, FOOTER_HTML, FOOTER_CONFIG, * FOOTER_BACKGROUND_COLOR, FOOTER_TEXT_COLOR * - Setup wizard: SETUP_WIZARD_COMPLETE, CONTENT_STORAGE - * - Quick search: QUICKSEARCH_FALLBACK_TARGET (tile-quick-search) + * - Quick search: QUICKSEARCH_FALLBACK_TARGET (tile-quick-search), + * SEARCH_SHORTCUTS (search-ai-prefix-shortcuts) * * @category Db * @package OCA\LaunchPad\Db @@ -62,4 +63,5 @@ enum AdminSettingKey: string { case MAX_DASHBOARDS_PER_USER = 'max_dashboards_per_user'; case MAX_WIDGETS_PER_DASHBOARD = 'max_widgets_per_dashboard'; case QUICKSEARCH_FALLBACK_TARGET = 'quicksearch_fallback_target'; + case SEARCH_SHORTCUTS = 'search_shortcuts'; }//end enum diff --git a/lib/Service/AdminSettingsService.php b/lib/Service/AdminSettingsService.php index 6188a6705..167e1ab28 100644 --- a/lib/Service/AdminSettingsService.php +++ b/lib/Service/AdminSettingsService.php @@ -264,8 +264,10 @@ public function isValidQuicksearchFallbackTarget(?string $value): bool { * * @return bool true when `$value` is a valid `https` URL template * containing `{query}`. + * + * @spec openspec/specs/tile-quick-search/spec.md */ - private function isValidQuicksearchFallbackUrlTemplate(string $value): bool { + public function isValidQuicksearchFallbackUrlTemplate(string $value): bool { if (trim($value) === '' || str_contains(haystack: $value, needle: '{query}') === false) { return false; } diff --git a/lib/Service/InitialStateBuilder.php b/lib/Service/InitialStateBuilder.php index b849806c1..1ebf80957 100644 --- a/lib/Service/InitialStateBuilder.php +++ b/lib/Service/InitialStateBuilder.php @@ -338,6 +338,22 @@ public function setQuicksearchFallbackTarget(string $fallbackTarget): self { return $this; }//end setQuicksearchFallbackTarget() + /** + * Set the search shortcuts every search box honours (search-ai-prefix-shortcuts, + * REQ-QSP-002). Optional key, like `quicksearchFallbackTarget`: the JS reader + * defaults it to an empty list. + * + * @param array $shortcuts Validated `{prefix, name, urlTemplate}` entries. + * + * @return self Fluent. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + public function setSearchShortcuts(array $shortcuts): self { + $this->values['searchShortcuts'] = array_values(array: $shortcuts); + return $this; + }//end setSearchShortcuts() + /** * Set every Nextcloud group (admin). * diff --git a/lib/Service/SearchShortcutService.php b/lib/Service/SearchShortcutService.php new file mode 100644 index 000000000..710610424 --- /dev/null +++ b/lib/Service/SearchShortcutService.php @@ -0,0 +1,150 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\AdminSettingKey; +use OCA\LaunchPad\Db\AdminSettingMapper; + +/** + * Read and validate the search shortcut list. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ +class SearchShortcutService { + /** + * Most shortcuts one instance may define. + * + * @var int + */ + public const MAX_SHORTCUTS = 30; + + /** + * A prefix: `!` and 1 to 10 letters or digits. + * + * @var string + */ + public const PREFIX_PATTERN = '/^![a-z0-9]{1,10}$/'; + + /** + * Constructor. + * + * @param AdminSettingMapper $settingMapper Admin settings store. + * @param AdminSettingsService $adminSettings Holds the https `{query}` template check. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + public function __construct( + private readonly AdminSettingMapper $settingMapper, + private readonly AdminSettingsService $adminSettings, + ) { + }//end __construct() + + /** + * The stored shortcuts. A stored list that no longer validates reads as + * empty, so the page never receives an address it must not open. + * + * @return array + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + public function getShortcuts(): array { + $raw = $this->settingMapper->getValue(key: AdminSettingKey::SEARCH_SHORTCUTS->value, default: []); + if (is_array(value: $raw) === false) { + return []; + } + + try { + return $this->validate(raw: $raw); + } catch (InvalidArgumentException) { + return []; + } + }//end getShortcuts() + + /** + * Validate and store the shortcuts. + * + * @param array $raw Shortcuts as sent by the admin screen. + * + * @return array The stored list. + * + * @throws InvalidArgumentException When an entry is invalid. + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + public function saveShortcuts(array $raw): array { + $shortcuts = $this->validate(raw: $raw); + $this->settingMapper->setSetting(key: AdminSettingKey::SEARCH_SHORTCUTS->value, value: $shortcuts); + + return $shortcuts; + }//end saveShortcuts() + + /** + * Check and normalise the list. + * + * @param array $raw Raw entries. + * + * @return array + * + * @throws InvalidArgumentException When an entry is invalid. + */ + private function validate(array $raw): array { + if (count(value: $raw) > self::MAX_SHORTCUTS) { + throw new InvalidArgumentException(message: 'At most ' . self::MAX_SHORTCUTS . ' search shortcuts'); + } + + $result = []; + $seen = []; + foreach (array_values(array: $raw) as $entry) { + if (is_array(value: $entry) === false) { + throw new InvalidArgumentException(message: 'A search shortcut must be an object'); + } + + $prefix = mb_strtolower(string: trim(string: (string)($entry['prefix'] ?? ''))); + if (preg_match(pattern: self::PREFIX_PATTERN, subject: $prefix) !== 1) { + throw new InvalidArgumentException(message: 'A prefix is ! followed by 1 to 10 letters or digits: ' . $prefix); + } + + if (isset($seen[$prefix]) === true) { + throw new InvalidArgumentException(message: 'Prefix used twice: ' . $prefix); + } + + $name = trim(string: (string)($entry['name'] ?? '')); + if ($name === '' || mb_strlen(string: $name) > 64) { + throw new InvalidArgumentException(message: 'A shortcut name must be 1 to 64 characters: ' . $prefix); + } + + $template = trim(string: (string)($entry['urlTemplate'] ?? '')); + if ($this->adminSettings->isValidQuicksearchFallbackUrlTemplate(value: $template) === false) { + throw new InvalidArgumentException(message: 'The address must be an https address containing {query}: ' . $prefix); + } + + $seen[$prefix] = true; + $result[] = ['prefix' => $prefix, 'name' => $name, 'urlTemplate' => $template]; + }//end foreach + + return $result; + }//end validate() +}//end class diff --git a/lib/actions.seed.json b/lib/actions.seed.json index f3c39cf91..5f717c98f 100644 --- a/lib/actions.seed.json +++ b/lib/actions.seed.json @@ -60,6 +60,8 @@ "metadata-admin.update-field": ["admin"], "metadata-admin.delete-field": ["admin"], "people-widget.get-users": ["admin"], + "search-shortcut.list": ["admin"], + "search-shortcut.save": ["admin"], "profile-fields.get-own": ["admin", "@all"], "profile-fields.save-own": ["admin", "@all"], "profile-fields.get-definitions": ["admin"], diff --git a/src/utils/loadInitialState.js b/src/utils/loadInitialState.js index 29a7ab48e..66522a4aa 100644 --- a/src/utils/loadInitialState.js +++ b/src/utils/loadInitialState.js @@ -63,6 +63,10 @@ const PAGE_KEYS = { // PHP side yet simply omits it, and 'none' is the safe default // (no navigation on no-match Enter). quicksearchFallbackTarget: 'none', + // search-ai-prefix-shortcuts REQ-QSP-002: the admin's search + // shortcuts `{prefix, name, urlTemplate}`. Optional key; an empty + // list keeps every search box as it was. + searchShortcuts: [], }, admin: { allGroups: [], diff --git a/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php b/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php index 129bb9211..de29ee609 100644 --- a/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php +++ b/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php @@ -64,6 +64,7 @@ private function makeController(WidgetService $widgetService): PageController { treeService: $this->createMock(DashboardTreeService::class), logger: $this->createMock(LoggerInterface::class), adminSettingsService: $this->createMock(AdminSettingsService::class), + searchShortcuts: $this->createMock(\OCA\LaunchPad\Service\SearchShortcutService::class), ); }//end makeController() diff --git a/tests/Unit/Service/ActionAuthBaselineTest.php b/tests/Unit/Service/ActionAuthBaselineTest.php index 6dc033c92..83f46704a 100644 --- a/tests/Unit/Service/ActionAuthBaselineTest.php +++ b/tests/Unit/Service/ActionAuthBaselineTest.php @@ -84,6 +84,8 @@ class ActionAuthBaselineTest extends TestCase { 'metadata-admin.update-field', 'metadata-admin.delete-field', 'people-widget.get-users', + 'search-shortcut.list', + 'search-shortcut.save', 'profile-fields.get-definitions', 'profile-fields.save-definitions', 'rule.get-rules', diff --git a/tests/Unit/Service/InitialStateBuilderTest.php b/tests/Unit/Service/InitialStateBuilderTest.php index ee7e7d3a6..5763c708e 100644 --- a/tests/Unit/Service/InitialStateBuilderTest.php +++ b/tests/Unit/Service/InitialStateBuilderTest.php @@ -29,6 +29,34 @@ use PHPUnit\Framework\TestCase; class InitialStateBuilderTest extends TestCase { + /** + * REQ-QSP-002: the search shortcuts reach the workspace page. + * + * @return void + */ + public function testSearchShortcutsReachTheWorkspace(): void { + $sink = []; + $state = $this->makeRecordingState($sink); + $shortcuts = [['prefix' => '!t', 'name' => 'TOPdesk', 'urlTemplate' => 'https://topdesk.example.nl/?q={query}']]; + + (new InitialStateBuilder(initialState: $state, page: Page::WORKSPACE)) + ->setWidgets([]) + ->setLayout([]) + ->setPrimaryGroup('g') + ->setPrimaryGroupName('G') + ->setIsAdmin(false) + ->setActiveDashboardId('') + ->setDashboardSource('group') + ->setGroupDashboards([]) + ->setUserDashboards([]) + ->setAllowUserDashboards(false) + ->setAllowedWidgets(null) + ->setSearchShortcuts($shortcuts) + ->apply(); + + $this->assertSame($shortcuts, $sink['searchShortcuts']); + }//end testSearchShortcutsReachTheWorkspace() + /** * In-memory IInitialState stub that records every push. * diff --git a/tests/Unit/Service/SearchShortcutServiceTest.php b/tests/Unit/Service/SearchShortcutServiceTest.php new file mode 100644 index 000000000..d31c9d225 --- /dev/null +++ b/tests/Unit/Service/SearchShortcutServiceTest.php @@ -0,0 +1,124 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\AdminSetting; +use OCA\LaunchPad\Db\AdminSettingMapper; +use OCA\LaunchPad\Service\AdminSettingsService; +use OCA\LaunchPad\Service\SearchShortcutService; +use PHPUnit\Framework\TestCase; + +class SearchShortcutServiceTest extends TestCase { + /** + * Stored admin settings. + * + * @var array + */ + private array $settings = []; + + private SearchShortcutService $service; + + protected function setUp(): void { + parent::setUp(); + + $mapper = $this->createMock(AdminSettingMapper::class); + $mapper->method('getValue')->willReturnCallback( + fn (string $key, mixed $default = null): mixed => $this->settings[$key] ?? $default + ); + $mapper->method('setSetting')->willReturnCallback( + function (string $key, mixed $value): AdminSetting { + $this->settings[$key] = $value; + return new AdminSetting(); + } + ); + + // The real template check, the one the quick-search fallback uses. + $adminSettings = new AdminSettingsService(settingMapper: $mapper); + + $this->service = new SearchShortcutService(settingMapper: $mapper, adminSettings: $adminSettings); + }//end setUp() + + public function testAValidListIsStoredWithLowerCasePrefixes(): void { + $saved = $this->service->saveShortcuts( + raw: [ + ['prefix' => '!T', 'name' => ' TOPdesk ', 'urlTemplate' => 'https://topdesk.example.nl/search?q={query}'], + ['prefix' => '!wiki', 'name' => 'Wikipedia', 'urlTemplate' => 'https://nl.wikipedia.org/w/index.php?search={query}'], + ] + ); + + $this->assertSame('!t', $saved[0]['prefix']); + $this->assertSame('TOPdesk', $saved[0]['name']); + $this->assertSame($saved, $this->service->getShortcuts()); + $this->assertSame($saved, $this->settings['search_shortcuts']); + }//end testAValidListIsStoredWithLowerCasePrefixes() + + /** + * @return array}> + */ + public static function invalidEntries(): array { + $template = 'https://example.nl/?q={query}'; + return [ + 'no bang' => [['prefix' => 't', 'name' => 'T', 'urlTemplate' => $template]], + 'too long' => [['prefix' => '!abcdefghijk', 'name' => 'T', 'urlTemplate' => $template]], + 'space in prefix' => [['prefix' => '!a b', 'name' => 'T', 'urlTemplate' => $template]], + 'no name' => [['prefix' => '!t', 'name' => ' ', 'urlTemplate' => $template]], + 'http' => [['prefix' => '!t', 'name' => 'T', 'urlTemplate' => 'http://example.nl/?q={query}']], + 'no placeholder' => [['prefix' => '!t', 'name' => 'T', 'urlTemplate' => 'https://example.nl/']], + 'javascript' => [['prefix' => '!t', 'name' => 'T', 'urlTemplate' => 'javascript:alert({query})']], + ]; + }//end invalidEntries() + + /** + * @dataProvider invalidEntries + */ + public function testAnInvalidEntryIsRefused(array $entry): void { + $this->expectException(InvalidArgumentException::class); + $this->service->saveShortcuts(raw: [$entry]); + }//end testAnInvalidEntryIsRefused() + + public function testPrefixesAreUniqueRegardlessOfCase(): void { + $this->expectException(InvalidArgumentException::class); + $this->service->saveShortcuts( + raw: [ + ['prefix' => '!t', 'name' => 'A', 'urlTemplate' => 'https://a.nl/?q={query}'], + ['prefix' => '!T', 'name' => 'B', 'urlTemplate' => 'https://b.nl/?q={query}'], + ] + ); + }//end testPrefixesAreUniqueRegardlessOfCase() + + public function testAtMostThirtyShortcuts(): void { + $raw = []; + for ($i = 0; $i < 31; $i++) { + $raw[] = ['prefix' => '!s' . $i, 'name' => 'S' . $i, 'urlTemplate' => 'https://s.nl/?q={query}']; + } + + $this->expectException(InvalidArgumentException::class); + $this->service->saveShortcuts(raw: $raw); + }//end testAtMostThirtyShortcuts() + + public function testAStoredListThatNoLongerValidatesReadsAsEmpty(): void { + $this->settings['search_shortcuts'] = [['prefix' => '!t', 'name' => 'T', 'urlTemplate' => 'http://insecure.nl/?q={query}']]; + + $this->assertSame([], $this->service->getShortcuts()); + }//end testAStoredListThatNoLongerValidatesReadsAsEmpty() +}//end class From af2c9ec62f6453f128a992f573e1cbb58f10e6f8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 08:55:11 +0200 Subject: [PATCH 05/21] feat(search): a known prefix sends the query straight to its site, and ? lists the shortcuts In the search widget a query whose first word is a shortcut prefix shows one result, Search TOPdesk for printer, and Enter opens the site in a new tab; an unknown prefix filters tiles as before; ! or ? lists the shortcuts. Administrators edit the list on a new admin tab. REQ-SPX-001..003 (search-ai-prefix-shortcuts) --- l10n/be.js | 14 +- l10n/be.json | 14 +- l10n/bg.js | 14 +- l10n/bg.json | 14 +- l10n/bs.js | 14 +- l10n/bs.json | 14 +- l10n/ca.js | 14 +- l10n/ca.json | 14 +- l10n/cs.js | 14 +- l10n/cs.json | 14 +- l10n/da.js | 14 +- l10n/da.json | 14 +- l10n/de.js | 14 +- l10n/de.json | 14 +- l10n/el.js | 14 +- l10n/el.json | 14 +- l10n/en.js | 14 +- l10n/en.json | 14 +- l10n/es.js | 14 +- l10n/es.json | 14 +- l10n/et.js | 14 +- l10n/et.json | 14 +- l10n/fi.js | 14 +- l10n/fi.json | 14 +- l10n/fr.js | 14 +- l10n/fr.json | 14 +- l10n/ga.js | 14 +- l10n/ga.json | 14 +- l10n/hr.js | 14 +- l10n/hr.json | 14 +- l10n/hu.js | 14 +- l10n/hu.json | 14 +- l10n/is.js | 14 +- l10n/is.json | 14 +- l10n/it.js | 14 +- l10n/it.json | 14 +- l10n/lb.js | 14 +- l10n/lb.json | 14 +- l10n/lt.js | 14 +- l10n/lt.json | 14 +- l10n/lv.js | 14 +- l10n/lv.json | 14 +- l10n/mk.js | 14 +- l10n/mk.json | 14 +- l10n/mt.js | 14 +- l10n/mt.json | 14 +- l10n/nb.js | 14 +- l10n/nb.json | 14 +- l10n/nl.js | 14 +- l10n/nl.json | 14 +- l10n/pl.js | 14 +- l10n/pl.json | 14 +- l10n/pt.js | 14 +- l10n/pt.json | 14 +- l10n/rm.js | 14 +- l10n/rm.json | 14 +- l10n/ro.js | 14 +- l10n/ro.json | 14 +- l10n/ru.js | 14 +- l10n/ru.json | 14 +- l10n/sk.js | 14 +- l10n/sk.json | 14 +- l10n/sl.js | 14 +- l10n/sl.json | 14 +- l10n/sq.js | 14 +- l10n/sq.json | 14 +- l10n/sr.js | 14 +- l10n/sr.json | 14 +- l10n/sv.js | 14 +- l10n/sv.json | 14 +- l10n/tr.js | 14 +- l10n/tr.json | 14 +- l10n/uk.js | 14 +- l10n/uk.json | 14 +- lib/Controller/PageController.php | 2 +- lib/Controller/SearchShortcutsController.php | 2 +- lib/Service/InitialStateBuilder.php | 2 +- lib/Service/SearchShortcutService.php | 4 +- src/components/RuntimeShellSearch.vue | 63 ++++- .../Widgets/Renderers/SearchWidget.vue | 10 + .../RuntimeShellSearch.shortcuts.spec.js | 138 +++++++++++ src/components/admin/AdminSettings.vue | 9 + .../admin/tabs/SearchShortcutsTab.vue | 217 ++++++++++++++++++ .../tabs/__tests__/SearchShortcutsTab.spec.js | 105 +++++++++ src/composables/useTileSearch.js | 110 ++++++++- src/utils/loadInitialState.js | 2 +- .../Unit/Service/InitialStateBuilderTest.php | 2 +- .../Service/SearchShortcutServiceTest.php | 2 +- 88 files changed, 1618 insertions(+), 86 deletions(-) create mode 100644 src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js create mode 100644 src/components/admin/tabs/SearchShortcutsTab.vue create mode 100644 src/components/admin/tabs/__tests__/SearchShortcutsTab.spec.js diff --git a/l10n/be.js b/l10n/be.js index 009cd5a99..76bf6f451 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -1323,6 +1323,18 @@ OC.L10N.register( "Alphabetically" : "Па алфавіце", "Most used" : "Найчасцей выкарыстоўваныя", "Last used" : "Апошнія выкарыстаныя", - "At random" : "Выпадкова" + "At random" : "Выпадкова", + "Search shortcuts" : "Ярлыкі пошуку", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Пошук, які пачынаецца з прэфікса, напрыклад !t printer, адразу адкрывае гэты сайт у новай укладцы. Увод ? у полі пошуку паказвае спіс ярлыкоў.", + "Prefix" : "Прэфікс", + "Address with {query}" : "Адрас з {query}", + "No search shortcuts yet." : "Ярлыкоў пошуку пакуль няма.", + "Search shortcuts saved." : "Ярлыкі пошуку захаваны.", + "Add shortcut" : "Дадаць ярлык", + "Save search shortcuts" : "Захаваць ярлыкі пошуку", + "The search shortcuts could not be loaded." : "Не ўдалося загрузіць ярлыкі пошуку.", + "The search shortcuts could not be saved." : "Не ўдалося захаваць ярлыкі пошуку.", + "Search {name} for {query}" : "Шукаць у {name}: {query}", + "Type what to search for in {name}" : "Увядзіце, што шукаць у {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/be.json b/l10n/be.json index 4b7180f08..3d594f825 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Па алфавіце", "Most used": "Найчасцей выкарыстоўваныя", "Last used": "Апошнія выкарыстаныя", - "At random": "Выпадкова" + "At random": "Выпадкова", + "Search shortcuts": "Ярлыкі пошуку", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Пошук, які пачынаецца з прэфікса, напрыклад !t printer, адразу адкрывае гэты сайт у новай укладцы. Увод ? у полі пошуку паказвае спіс ярлыкоў.", + "Prefix": "Прэфікс", + "Address with {query}": "Адрас з {query}", + "No search shortcuts yet.": "Ярлыкоў пошуку пакуль няма.", + "Search shortcuts saved.": "Ярлыкі пошуку захаваны.", + "Add shortcut": "Дадаць ярлык", + "Save search shortcuts": "Захаваць ярлыкі пошуку", + "The search shortcuts could not be loaded.": "Не ўдалося загрузіць ярлыкі пошуку.", + "The search shortcuts could not be saved.": "Не ўдалося захаваць ярлыкі пошуку.", + "Search {name} for {query}": "Шукаць у {name}: {query}", + "Type what to search for in {name}": "Увядзіце, што шукаць у {name}" } } diff --git a/l10n/bg.js b/l10n/bg.js index 29818f4a3..d25241b77 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "По азбучен ред", "Most used" : "Най-използвани", "Last used" : "Последно използвани", - "At random" : "В случаен ред" + "At random" : "В случаен ред", + "Search shortcuts" : "Преки пътища за търсене", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Търсене, което започва с префикс, например !t printer, отива направо към този сайт в нов раздел. Въвеждането на ? в поле за търсене показва преките пътища.", + "Prefix" : "Префикс", + "Address with {query}" : "Адрес с {query}", + "No search shortcuts yet." : "Все още няма преки пътища за търсене.", + "Search shortcuts saved." : "Преките пътища за търсене са запазени.", + "Add shortcut" : "Добавяне на пряк път", + "Save search shortcuts" : "Запазване на преките пътища за търсене", + "The search shortcuts could not be loaded." : "Преките пътища за търсене не можаха да се заредят.", + "The search shortcuts could not be saved." : "Преките пътища за търсене не можаха да се запазят.", + "Search {name} for {query}" : "Търсене в {name} за {query}", + "Type what to search for in {name}" : "Въведете какво да търсите в {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/bg.json b/l10n/bg.json index 0879485b6..56adfe841 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -1299,6 +1299,18 @@ "Alphabetically": "По азбучен ред", "Most used": "Най-използвани", "Last used": "Последно използвани", - "At random": "В случаен ред" + "At random": "В случаен ред", + "Search shortcuts": "Преки пътища за търсене", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Търсене, което започва с префикс, например !t printer, отива направо към този сайт в нов раздел. Въвеждането на ? в поле за търсене показва преките пътища.", + "Prefix": "Префикс", + "Address with {query}": "Адрес с {query}", + "No search shortcuts yet.": "Все още няма преки пътища за търсене.", + "Search shortcuts saved.": "Преките пътища за търсене са запазени.", + "Add shortcut": "Добавяне на пряк път", + "Save search shortcuts": "Запазване на преките пътища за търсене", + "The search shortcuts could not be loaded.": "Преките пътища за търсене не можаха да се заредят.", + "The search shortcuts could not be saved.": "Преките пътища за търсене не можаха да се запазят.", + "Search {name} for {query}": "Търсене в {name} за {query}", + "Type what to search for in {name}": "Въведете какво да търсите в {name}" } } diff --git a/l10n/bs.js b/l10n/bs.js index 1f9c11827..62caff64e 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Abecedno", "Most used" : "Najčešće korišteno", "Last used" : "Zadnje korišteno", - "At random" : "Nasumično" + "At random" : "Nasumično", + "Search shortcuts" : "Prečaci za pretragu", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Pretraga koja počinje prefiksom, npr. !t printer, ide ravno na tu stranicu u novoj kartici. Upisivanje ? u polje za pretragu prikazuje prečace.", + "Prefix" : "Prefiks", + "Address with {query}" : "Adresa s {query}", + "No search shortcuts yet." : "Još nema prečaca za pretragu.", + "Search shortcuts saved." : "Prečaci za pretragu su spremljeni.", + "Add shortcut" : "Dodaj prečac", + "Save search shortcuts" : "Spremi prečace za pretragu", + "The search shortcuts could not be loaded." : "Prečaci za pretragu nisu mogli biti učitani.", + "The search shortcuts could not be saved." : "Prečaci za pretragu nisu mogli biti spremljeni.", + "Search {name} for {query}" : "Pretraži {name} za {query}", + "Type what to search for in {name}" : "Upišite što tražite u {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/bs.json b/l10n/bs.json index 4c194ed67..97aa7826f 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Abecedno", "Most used": "Najčešće korišteno", "Last used": "Zadnje korišteno", - "At random": "Nasumično" + "At random": "Nasumično", + "Search shortcuts": "Prečaci za pretragu", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Pretraga koja počinje prefiksom, npr. !t printer, ide ravno na tu stranicu u novoj kartici. Upisivanje ? u polje za pretragu prikazuje prečace.", + "Prefix": "Prefiks", + "Address with {query}": "Adresa s {query}", + "No search shortcuts yet.": "Još nema prečaca za pretragu.", + "Search shortcuts saved.": "Prečaci za pretragu su spremljeni.", + "Add shortcut": "Dodaj prečac", + "Save search shortcuts": "Spremi prečace za pretragu", + "The search shortcuts could not be loaded.": "Prečaci za pretragu nisu mogli biti učitani.", + "The search shortcuts could not be saved.": "Prečaci za pretragu nisu mogli biti spremljeni.", + "Search {name} for {query}": "Pretraži {name} za {query}", + "Type what to search for in {name}": "Upišite što tražite u {name}" } } diff --git a/l10n/ca.js b/l10n/ca.js index 86332cdff..378302230 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabèticament", "Most used" : "Els més usats", "Last used" : "Darrers usats", - "At random" : "A l'atzar" + "At random" : "A l'atzar", + "Search shortcuts" : "Dreceres de cerca", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Una cerca que comença amb un prefix, com ara !t printer, va directament a aquell lloc en una pestanya nova. Escriure ? en un quadre de cerca mostra les dreceres.", + "Prefix" : "Prefix", + "Address with {query}" : "Adreça amb {query}", + "No search shortcuts yet." : "Encara no hi ha dreceres de cerca.", + "Search shortcuts saved." : "S'han desat les dreceres de cerca.", + "Add shortcut" : "Afegeix una drecera", + "Save search shortcuts" : "Desa les dreceres de cerca", + "The search shortcuts could not be loaded." : "No s'han pogut carregar les dreceres de cerca.", + "The search shortcuts could not be saved." : "No s'han pogut desar les dreceres de cerca.", + "Search {name} for {query}" : "Cerca {query} a {name}", + "Type what to search for in {name}" : "Escriviu què voleu cercar a {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/ca.json b/l10n/ca.json index 8b4868e41..7b3a43c40 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabèticament", "Most used": "Els més usats", "Last used": "Darrers usats", - "At random": "A l'atzar" + "At random": "A l'atzar", + "Search shortcuts": "Dreceres de cerca", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Una cerca que comença amb un prefix, com ara !t printer, va directament a aquell lloc en una pestanya nova. Escriure ? en un quadre de cerca mostra les dreceres.", + "Prefix": "Prefix", + "Address with {query}": "Adreça amb {query}", + "No search shortcuts yet.": "Encara no hi ha dreceres de cerca.", + "Search shortcuts saved.": "S'han desat les dreceres de cerca.", + "Add shortcut": "Afegeix una drecera", + "Save search shortcuts": "Desa les dreceres de cerca", + "The search shortcuts could not be loaded.": "No s'han pogut carregar les dreceres de cerca.", + "The search shortcuts could not be saved.": "No s'han pogut desar les dreceres de cerca.", + "Search {name} for {query}": "Cerca {query} a {name}", + "Type what to search for in {name}": "Escriviu què voleu cercar a {name}" } } diff --git a/l10n/cs.js b/l10n/cs.js index e5c89f275..a1e4d098a 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Abecedně", "Most used" : "Nejpoužívanější", "Last used" : "Naposledy použité", - "At random" : "Náhodně" + "At random" : "Náhodně", + "Search shortcuts" : "Zkratky vyhledávání", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Hledání začínající předponou, například !t printer, přejde rovnou na daný web v nové kartě. Napsáním ? do vyhledávacího pole zobrazíte zkratky.", + "Prefix" : "Předpona", + "Address with {query}" : "Adresa s {query}", + "No search shortcuts yet." : "Zatím žádné zkratky vyhledávání.", + "Search shortcuts saved." : "Zkratky vyhledávání jsou uloženy.", + "Add shortcut" : "Přidat zkratku", + "Save search shortcuts" : "Uložit zkratky vyhledávání", + "The search shortcuts could not be loaded." : "Zkratky vyhledávání se nepodařilo načíst.", + "The search shortcuts could not be saved." : "Zkratky vyhledávání se nepodařilo uložit.", + "Search {name} for {query}" : "Hledat {query} v {name}", + "Type what to search for in {name}" : "Napište, co hledat v {name}" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/cs.json b/l10n/cs.json index 6b523cb30..a9f386104 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Abecedně", "Most used": "Nejpoužívanější", "Last used": "Naposledy použité", - "At random": "Náhodně" + "At random": "Náhodně", + "Search shortcuts": "Zkratky vyhledávání", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Hledání začínající předponou, například !t printer, přejde rovnou na daný web v nové kartě. Napsáním ? do vyhledávacího pole zobrazíte zkratky.", + "Prefix": "Předpona", + "Address with {query}": "Adresa s {query}", + "No search shortcuts yet.": "Zatím žádné zkratky vyhledávání.", + "Search shortcuts saved.": "Zkratky vyhledávání jsou uloženy.", + "Add shortcut": "Přidat zkratku", + "Save search shortcuts": "Uložit zkratky vyhledávání", + "The search shortcuts could not be loaded.": "Zkratky vyhledávání se nepodařilo načíst.", + "The search shortcuts could not be saved.": "Zkratky vyhledávání se nepodařilo uložit.", + "Search {name} for {query}": "Hledat {query} v {name}", + "Type what to search for in {name}": "Napište, co hledat v {name}" } } diff --git a/l10n/da.js b/l10n/da.js index dde69e192..eab9196d1 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetisk", "Most used" : "Mest brugte", "Last used" : "Senest brugte", - "At random" : "Tilfældigt" + "At random" : "Tilfældigt", + "Search shortcuts" : "Søgegenveje", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "En søgning, der starter med et præfiks, fx !t printer, går direkte til det websted i en ny fane. Skriv ? i et søgefelt for at se genvejene.", + "Prefix" : "Præfiks", + "Address with {query}" : "Adresse med {query}", + "No search shortcuts yet." : "Ingen søgegenveje endnu.", + "Search shortcuts saved." : "Søgegenvejene er gemt.", + "Add shortcut" : "Tilføj genvej", + "Save search shortcuts" : "Gem søgegenveje", + "The search shortcuts could not be loaded." : "Søgegenvejene kunne ikke indlæses.", + "The search shortcuts could not be saved." : "Søgegenvejene kunne ikke gemmes.", + "Search {name} for {query}" : "Søg i {name} efter {query}", + "Type what to search for in {name}" : "Skriv, hvad du vil søge efter i {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/da.json b/l10n/da.json index 840976e06..0764f8f9e 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabetisk", "Most used": "Mest brugte", "Last used": "Senest brugte", - "At random": "Tilfældigt" + "At random": "Tilfældigt", + "Search shortcuts": "Søgegenveje", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "En søgning, der starter med et præfiks, fx !t printer, går direkte til det websted i en ny fane. Skriv ? i et søgefelt for at se genvejene.", + "Prefix": "Præfiks", + "Address with {query}": "Adresse med {query}", + "No search shortcuts yet.": "Ingen søgegenveje endnu.", + "Search shortcuts saved.": "Søgegenvejene er gemt.", + "Add shortcut": "Tilføj genvej", + "Save search shortcuts": "Gem søgegenveje", + "The search shortcuts could not be loaded.": "Søgegenvejene kunne ikke indlæses.", + "The search shortcuts could not be saved.": "Søgegenvejene kunne ikke gemmes.", + "Search {name} for {query}": "Søg i {name} efter {query}", + "Type what to search for in {name}": "Skriv, hvad du vil søge efter i {name}" } } diff --git a/l10n/de.js b/l10n/de.js index 9e008de03..bb53c9f5e 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alphabetisch", "Most used" : "Am häufigsten genutzt", "Last used" : "Zuletzt genutzt", - "At random" : "Zufällig" + "At random" : "Zufällig", + "Search shortcuts" : "Suchkürzel", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Eine Suche, die mit einem Präfix beginnt, etwa !t printer, geht direkt in einem neuen Tab zu dieser Seite. Ein ? im Suchfeld listet die Kürzel auf.", + "Prefix" : "Präfix", + "Address with {query}" : "Adresse mit {query}", + "No search shortcuts yet." : "Noch keine Suchkürzel.", + "Search shortcuts saved." : "Suchkürzel gespeichert.", + "Add shortcut" : "Kürzel hinzufügen", + "Save search shortcuts" : "Suchkürzel speichern", + "The search shortcuts could not be loaded." : "Die Suchkürzel konnten nicht geladen werden.", + "The search shortcuts could not be saved." : "Die Suchkürzel konnten nicht gespeichert werden.", + "Search {name} for {query}" : "In {name} nach {query} suchen", + "Type what to search for in {name}" : "Gib ein, wonach in {name} gesucht werden soll" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/de.json b/l10n/de.json index bf84b97e3..a4b1b008e 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alphabetisch", "Most used": "Am häufigsten genutzt", "Last used": "Zuletzt genutzt", - "At random": "Zufällig" + "At random": "Zufällig", + "Search shortcuts": "Suchkürzel", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Eine Suche, die mit einem Präfix beginnt, etwa !t printer, geht direkt in einem neuen Tab zu dieser Seite. Ein ? im Suchfeld listet die Kürzel auf.", + "Prefix": "Präfix", + "Address with {query}": "Adresse mit {query}", + "No search shortcuts yet.": "Noch keine Suchkürzel.", + "Search shortcuts saved.": "Suchkürzel gespeichert.", + "Add shortcut": "Kürzel hinzufügen", + "Save search shortcuts": "Suchkürzel speichern", + "The search shortcuts could not be loaded.": "Die Suchkürzel konnten nicht geladen werden.", + "The search shortcuts could not be saved.": "Die Suchkürzel konnten nicht gespeichert werden.", + "Search {name} for {query}": "In {name} nach {query} suchen", + "Type what to search for in {name}": "Gib ein, wonach in {name} gesucht werden soll" } } diff --git a/l10n/el.js b/l10n/el.js index 15a512709..b437cd029 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Αλφαβητικά", "Most used" : "Συχνότερα χρησιμοποιούμενα", "Last used" : "Τελευταία χρήση", - "At random" : "Τυχαία" + "At random" : "Τυχαία", + "Search shortcuts" : "Συντομεύσεις αναζήτησης", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Μια αναζήτηση που ξεκινά με πρόθεμα, όπως !t printer, πηγαίνει κατευθείαν σε εκείνη την τοποθεσία σε νέα καρτέλα. Πληκτρολογώντας ? σε ένα πεδίο αναζήτησης εμφανίζονται οι συντομεύσεις.", + "Prefix" : "Πρόθεμα", + "Address with {query}" : "Διεύθυνση με {query}", + "No search shortcuts yet." : "Δεν υπάρχουν ακόμα συντομεύσεις αναζήτησης.", + "Search shortcuts saved." : "Οι συντομεύσεις αναζήτησης αποθηκεύτηκαν.", + "Add shortcut" : "Προσθήκη συντόμευσης", + "Save search shortcuts" : "Αποθήκευση συντομεύσεων αναζήτησης", + "The search shortcuts could not be loaded." : "Δεν ήταν δυνατή η φόρτωση των συντομεύσεων αναζήτησης.", + "The search shortcuts could not be saved." : "Δεν ήταν δυνατή η αποθήκευση των συντομεύσεων αναζήτησης.", + "Search {name} for {query}" : "Αναζήτηση στο {name} για {query}", + "Type what to search for in {name}" : "Πληκτρολογήστε τι να αναζητηθεί στο {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/el.json b/l10n/el.json index 5c8950f27..372eb89de 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Αλφαβητικά", "Most used": "Συχνότερα χρησιμοποιούμενα", "Last used": "Τελευταία χρήση", - "At random": "Τυχαία" + "At random": "Τυχαία", + "Search shortcuts": "Συντομεύσεις αναζήτησης", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Μια αναζήτηση που ξεκινά με πρόθεμα, όπως !t printer, πηγαίνει κατευθείαν σε εκείνη την τοποθεσία σε νέα καρτέλα. Πληκτρολογώντας ? σε ένα πεδίο αναζήτησης εμφανίζονται οι συντομεύσεις.", + "Prefix": "Πρόθεμα", + "Address with {query}": "Διεύθυνση με {query}", + "No search shortcuts yet.": "Δεν υπάρχουν ακόμα συντομεύσεις αναζήτησης.", + "Search shortcuts saved.": "Οι συντομεύσεις αναζήτησης αποθηκεύτηκαν.", + "Add shortcut": "Προσθήκη συντόμευσης", + "Save search shortcuts": "Αποθήκευση συντομεύσεων αναζήτησης", + "The search shortcuts could not be loaded.": "Δεν ήταν δυνατή η φόρτωση των συντομεύσεων αναζήτησης.", + "The search shortcuts could not be saved.": "Δεν ήταν δυνατή η αποθήκευση των συντομεύσεων αναζήτησης.", + "Search {name} for {query}": "Αναζήτηση στο {name} για {query}", + "Type what to search for in {name}": "Πληκτρολογήστε τι να αναζητηθεί στο {name}" } } diff --git a/l10n/en.js b/l10n/en.js index b2c2ba066..031b339f7 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -1312,7 +1312,19 @@ OC.L10N.register( "Alphabetically" : "Alphabetically", "Most used" : "Most used", "Last used" : "Last used", - "At random" : "At random" + "At random" : "At random", + "Search shortcuts" : "Search shortcuts", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.", + "Prefix" : "Prefix", + "Address with {query}" : "Address with {query}", + "No search shortcuts yet." : "No search shortcuts yet.", + "Search shortcuts saved." : "Search shortcuts saved.", + "Add shortcut" : "Add shortcut", + "Save search shortcuts" : "Save search shortcuts", + "The search shortcuts could not be loaded." : "The search shortcuts could not be loaded.", + "The search shortcuts could not be saved." : "The search shortcuts could not be saved.", + "Search {name} for {query}" : "Search {name} for {query}", + "Type what to search for in {name}" : "Type what to search for in {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/en.json b/l10n/en.json index 28106e6b2..74b6aa512 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -1277,6 +1277,18 @@ "Alphabetically": "Alphabetically", "Most used": "Most used", "Last used": "Last used", - "At random": "At random" + "At random": "At random", + "Search shortcuts": "Search shortcuts", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.", + "Prefix": "Prefix", + "Address with {query}": "Address with {query}", + "No search shortcuts yet.": "No search shortcuts yet.", + "Search shortcuts saved.": "Search shortcuts saved.", + "Add shortcut": "Add shortcut", + "Save search shortcuts": "Save search shortcuts", + "The search shortcuts could not be loaded.": "The search shortcuts could not be loaded.", + "The search shortcuts could not be saved.": "The search shortcuts could not be saved.", + "Search {name} for {query}": "Search {name} for {query}", + "Type what to search for in {name}": "Type what to search for in {name}" } } diff --git a/l10n/es.js b/l10n/es.js index 75312eba8..63d8be189 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabéticamente", "Most used" : "Más usados", "Last used" : "Último uso", - "At random" : "Al azar" + "At random" : "Al azar", + "Search shortcuts" : "Atajos de búsqueda", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Una búsqueda que empieza con un prefijo, como !t printer, va directamente a ese sitio en una pestaña nueva. Escribir ? en un cuadro de búsqueda muestra los atajos.", + "Prefix" : "Prefijo", + "Address with {query}" : "Dirección con {query}", + "No search shortcuts yet." : "Aún no hay atajos de búsqueda.", + "Search shortcuts saved." : "Atajos de búsqueda guardados.", + "Add shortcut" : "Añadir atajo", + "Save search shortcuts" : "Guardar atajos de búsqueda", + "The search shortcuts could not be loaded." : "No se pudieron cargar los atajos de búsqueda.", + "The search shortcuts could not be saved." : "No se pudieron guardar los atajos de búsqueda.", + "Search {name} for {query}" : "Buscar {query} en {name}", + "Type what to search for in {name}" : "Escribe qué buscar en {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/es.json b/l10n/es.json index c63e2e707..35b660cdd 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabéticamente", "Most used": "Más usados", "Last used": "Último uso", - "At random": "Al azar" + "At random": "Al azar", + "Search shortcuts": "Atajos de búsqueda", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Una búsqueda que empieza con un prefijo, como !t printer, va directamente a ese sitio en una pestaña nueva. Escribir ? en un cuadro de búsqueda muestra los atajos.", + "Prefix": "Prefijo", + "Address with {query}": "Dirección con {query}", + "No search shortcuts yet.": "Aún no hay atajos de búsqueda.", + "Search shortcuts saved.": "Atajos de búsqueda guardados.", + "Add shortcut": "Añadir atajo", + "Save search shortcuts": "Guardar atajos de búsqueda", + "The search shortcuts could not be loaded.": "No se pudieron cargar los atajos de búsqueda.", + "The search shortcuts could not be saved.": "No se pudieron guardar los atajos de búsqueda.", + "Search {name} for {query}": "Buscar {query} en {name}", + "Type what to search for in {name}": "Escribe qué buscar en {name}" } } diff --git a/l10n/et.js b/l10n/et.js index 02d6525db..08aaa28d2 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Tähestiku järgi", "Most used" : "Enim kasutatud", "Last used" : "Viimati kasutatud", - "At random" : "Juhuslikult" + "At random" : "Juhuslikult", + "Search shortcuts" : "Otsingu otseteed", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Eesliitega algav otsing, näiteks !t printer, viib otse sellele saidile uuel vahekaardil. Sisesta otsingukasti ?, et näha otseteid.", + "Prefix" : "Eesliide", + "Address with {query}" : "Aadress koos {query}", + "No search shortcuts yet." : "Otsingu otseteid veel pole.", + "Search shortcuts saved." : "Otsingu otseteed salvestatud.", + "Add shortcut" : "Lisa otsetee", + "Save search shortcuts" : "Salvesta otsingu otseteed", + "The search shortcuts could not be loaded." : "Otsingu otseteid ei õnnestunud laadida.", + "The search shortcuts could not be saved." : "Otsingu otseteid ei õnnestunud salvestada.", + "Search {name} for {query}" : "Otsi {name} kaudu: {query}", + "Type what to search for in {name}" : "Sisesta, mida {name} kaudu otsida" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/et.json b/l10n/et.json index d6760c3fc..d5e64522d 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Tähestiku järgi", "Most used": "Enim kasutatud", "Last used": "Viimati kasutatud", - "At random": "Juhuslikult" + "At random": "Juhuslikult", + "Search shortcuts": "Otsingu otseteed", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Eesliitega algav otsing, näiteks !t printer, viib otse sellele saidile uuel vahekaardil. Sisesta otsingukasti ?, et näha otseteid.", + "Prefix": "Eesliide", + "Address with {query}": "Aadress koos {query}", + "No search shortcuts yet.": "Otsingu otseteid veel pole.", + "Search shortcuts saved.": "Otsingu otseteed salvestatud.", + "Add shortcut": "Lisa otsetee", + "Save search shortcuts": "Salvesta otsingu otseteed", + "The search shortcuts could not be loaded.": "Otsingu otseteid ei õnnestunud laadida.", + "The search shortcuts could not be saved.": "Otsingu otseteid ei õnnestunud salvestada.", + "Search {name} for {query}": "Otsi {name} kaudu: {query}", + "Type what to search for in {name}": "Sisesta, mida {name} kaudu otsida" } } diff --git a/l10n/fi.js b/l10n/fi.js index a0949456f..94bc9489c 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Aakkosjärjestyksessä", "Most used" : "Eniten käytetyt", "Last used" : "Viimeksi käytetyt", - "At random" : "Satunnaisesti" + "At random" : "Satunnaisesti", + "Search shortcuts" : "Hakuoikotiet", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Etuliitteellä alkava haku, kuten !t printer, avaa kyseisen sivuston suoraan uuteen välilehteen. Kirjoita hakukenttään ?, niin näet oikotiet.", + "Prefix" : "Etuliite", + "Address with {query}" : "Osoite, jossa {query}", + "No search shortcuts yet." : "Hakuoikoteitä ei vielä ole.", + "Search shortcuts saved." : "Hakuoikotiet tallennettu.", + "Add shortcut" : "Lisää oikotie", + "Save search shortcuts" : "Tallenna hakuoikotiet", + "The search shortcuts could not be loaded." : "Hakuoikoteitä ei voitu ladata.", + "The search shortcuts could not be saved." : "Hakuoikoteitä ei voitu tallentaa.", + "Search {name} for {query}" : "Hae {name}-palvelusta: {query}", + "Type what to search for in {name}" : "Kirjoita, mitä haetaan palvelusta {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/fi.json b/l10n/fi.json index 4cd802cbf..fd9488a93 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Aakkosjärjestyksessä", "Most used": "Eniten käytetyt", "Last used": "Viimeksi käytetyt", - "At random": "Satunnaisesti" + "At random": "Satunnaisesti", + "Search shortcuts": "Hakuoikotiet", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Etuliitteellä alkava haku, kuten !t printer, avaa kyseisen sivuston suoraan uuteen välilehteen. Kirjoita hakukenttään ?, niin näet oikotiet.", + "Prefix": "Etuliite", + "Address with {query}": "Osoite, jossa {query}", + "No search shortcuts yet.": "Hakuoikoteitä ei vielä ole.", + "Search shortcuts saved.": "Hakuoikotiet tallennettu.", + "Add shortcut": "Lisää oikotie", + "Save search shortcuts": "Tallenna hakuoikotiet", + "The search shortcuts could not be loaded.": "Hakuoikoteitä ei voitu ladata.", + "The search shortcuts could not be saved.": "Hakuoikoteitä ei voitu tallentaa.", + "Search {name} for {query}": "Hae {name}-palvelusta: {query}", + "Type what to search for in {name}": "Kirjoita, mitä haetaan palvelusta {name}" } } diff --git a/l10n/fr.js b/l10n/fr.js index 7331b0f10..c380e5e21 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Par ordre alphabétique", "Most used" : "Les plus utilisés", "Last used" : "Dernière utilisation", - "At random" : "Au hasard" + "At random" : "Au hasard", + "Search shortcuts" : "Raccourcis de recherche", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Une recherche qui commence par un préfixe, comme !t printer, va directement sur ce site dans un nouvel onglet. Saisir ? dans un champ de recherche affiche les raccourcis.", + "Prefix" : "Préfixe", + "Address with {query}" : "Adresse avec {query}", + "No search shortcuts yet." : "Aucun raccourci de recherche pour l'instant.", + "Search shortcuts saved." : "Raccourcis de recherche enregistrés.", + "Add shortcut" : "Ajouter un raccourci", + "Save search shortcuts" : "Enregistrer les raccourcis de recherche", + "The search shortcuts could not be loaded." : "Les raccourcis de recherche n'ont pas pu être chargés.", + "The search shortcuts could not be saved." : "Les raccourcis de recherche n'ont pas pu être enregistrés.", + "Search {name} for {query}" : "Rechercher {query} dans {name}", + "Type what to search for in {name}" : "Saisissez ce que vous cherchez dans {name}" }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/fr.json b/l10n/fr.json index 7e5ff6f4d..fe91c3213 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Par ordre alphabétique", "Most used": "Les plus utilisés", "Last used": "Dernière utilisation", - "At random": "Au hasard" + "At random": "Au hasard", + "Search shortcuts": "Raccourcis de recherche", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Une recherche qui commence par un préfixe, comme !t printer, va directement sur ce site dans un nouvel onglet. Saisir ? dans un champ de recherche affiche les raccourcis.", + "Prefix": "Préfixe", + "Address with {query}": "Adresse avec {query}", + "No search shortcuts yet.": "Aucun raccourci de recherche pour l'instant.", + "Search shortcuts saved.": "Raccourcis de recherche enregistrés.", + "Add shortcut": "Ajouter un raccourci", + "Save search shortcuts": "Enregistrer les raccourcis de recherche", + "The search shortcuts could not be loaded.": "Les raccourcis de recherche n'ont pas pu être chargés.", + "The search shortcuts could not be saved.": "Les raccourcis de recherche n'ont pas pu être enregistrés.", + "Search {name} for {query}": "Rechercher {query} dans {name}", + "Type what to search for in {name}": "Saisissez ce que vous cherchez dans {name}" } } diff --git a/l10n/ga.js b/l10n/ga.js index 5a43ce7fa..68b2ea329 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "In ord aibítre", "Most used" : "Is mó a úsáidtear", "Last used" : "Úsáidte is déanaí", - "At random" : "Go randamach" + "At random" : "Go randamach", + "Search shortcuts" : "Aicearraí cuardaigh", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Téann cuardach a thosaíonn le réimír, mar shampla !t printer, díreach chuig an suíomh sin i gcluaisín nua. Clóscríobh ? i mbosca cuardaigh chun na haicearraí a liostú.", + "Prefix" : "Réimír", + "Address with {query}" : "Seoladh le {query}", + "No search shortcuts yet." : "Níl aon aicearraí cuardaigh fós.", + "Search shortcuts saved." : "Aicearraí cuardaigh sábháilte.", + "Add shortcut" : "Cuir aicearra leis", + "Save search shortcuts" : "Sábháil aicearraí cuardaigh", + "The search shortcuts could not be loaded." : "Níorbh fhéidir na haicearraí cuardaigh a lódáil.", + "The search shortcuts could not be saved." : "Níorbh fhéidir na haicearraí cuardaigh a shábháil.", + "Search {name} for {query}" : "Cuardaigh {name} le haghaidh {query}", + "Type what to search for in {name}" : "Clóscríobh cad atá le cuardach in {name}" }, "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);" ); diff --git a/l10n/ga.json b/l10n/ga.json index 4f4caf134..fb5ce194a 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -1302,6 +1302,18 @@ "Alphabetically": "In ord aibítre", "Most used": "Is mó a úsáidtear", "Last used": "Úsáidte is déanaí", - "At random": "Go randamach" + "At random": "Go randamach", + "Search shortcuts": "Aicearraí cuardaigh", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Téann cuardach a thosaíonn le réimír, mar shampla !t printer, díreach chuig an suíomh sin i gcluaisín nua. Clóscríobh ? i mbosca cuardaigh chun na haicearraí a liostú.", + "Prefix": "Réimír", + "Address with {query}": "Seoladh le {query}", + "No search shortcuts yet.": "Níl aon aicearraí cuardaigh fós.", + "Search shortcuts saved.": "Aicearraí cuardaigh sábháilte.", + "Add shortcut": "Cuir aicearra leis", + "Save search shortcuts": "Sábháil aicearraí cuardaigh", + "The search shortcuts could not be loaded.": "Níorbh fhéidir na haicearraí cuardaigh a lódáil.", + "The search shortcuts could not be saved.": "Níorbh fhéidir na haicearraí cuardaigh a shábháil.", + "Search {name} for {query}": "Cuardaigh {name} le haghaidh {query}", + "Type what to search for in {name}": "Clóscríobh cad atá le cuardach in {name}" } } diff --git a/l10n/hr.js b/l10n/hr.js index 7bc09558f..4035bd801 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Abecedno", "Most used" : "Najčešće korišteno", "Last used" : "Posljednje korišteno", - "At random" : "Nasumično" + "At random" : "Nasumično", + "Search shortcuts" : "Prečaci za pretraživanje", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Pretraživanje koje počinje prefiksom, npr. !t printer, ide izravno na tu stranicu u novoj kartici. Upisivanjem ? u okvir za pretraživanje prikazuju se prečaci.", + "Prefix" : "Prefiks", + "Address with {query}" : "Adresa s {query}", + "No search shortcuts yet." : "Još nema prečaca za pretraživanje.", + "Search shortcuts saved." : "Prečaci za pretraživanje su spremljeni.", + "Add shortcut" : "Dodaj prečac", + "Save search shortcuts" : "Spremi prečace za pretraživanje", + "The search shortcuts could not be loaded." : "Prečace za pretraživanje nije bilo moguće učitati.", + "The search shortcuts could not be saved." : "Prečace za pretraživanje nije bilo moguće spremiti.", + "Search {name} for {query}" : "Pretraži {name} za {query}", + "Type what to search for in {name}" : "Upišite što tražite u {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/hr.json b/l10n/hr.json index 5abd69a6c..8ad434479 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Abecedno", "Most used": "Najčešće korišteno", "Last used": "Posljednje korišteno", - "At random": "Nasumično" + "At random": "Nasumično", + "Search shortcuts": "Prečaci za pretraživanje", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Pretraživanje koje počinje prefiksom, npr. !t printer, ide izravno na tu stranicu u novoj kartici. Upisivanjem ? u okvir za pretraživanje prikazuju se prečaci.", + "Prefix": "Prefiks", + "Address with {query}": "Adresa s {query}", + "No search shortcuts yet.": "Još nema prečaca za pretraživanje.", + "Search shortcuts saved.": "Prečaci za pretraživanje su spremljeni.", + "Add shortcut": "Dodaj prečac", + "Save search shortcuts": "Spremi prečace za pretraživanje", + "The search shortcuts could not be loaded.": "Prečace za pretraživanje nije bilo moguće učitati.", + "The search shortcuts could not be saved.": "Prečace za pretraživanje nije bilo moguće spremiti.", + "Search {name} for {query}": "Pretraži {name} za {query}", + "Type what to search for in {name}": "Upišite što tražite u {name}" } } diff --git a/l10n/hu.js b/l10n/hu.js index 8f267be02..082ef33c8 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Betűrendben", "Most used" : "Leggyakrabban használt", "Last used" : "Legutóbb használt", - "At random" : "Véletlenszerűen" + "At random" : "Véletlenszerűen", + "Search shortcuts" : "Keresési parancsikonok", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Az előtaggal kezdődő keresés, például !t printer, közvetlenül az adott oldalra visz egy új lapon. Ha ? jelet írsz a keresőmezőbe, megjelennek a parancsikonok.", + "Prefix" : "Előtag", + "Address with {query}" : "Cím {query} jelöléssel", + "No search shortcuts yet." : "Még nincsenek keresési parancsikonok.", + "Search shortcuts saved." : "Keresési parancsikonok mentve.", + "Add shortcut" : "Parancsikon hozzáadása", + "Save search shortcuts" : "Keresési parancsikonok mentése", + "The search shortcuts could not be loaded." : "A keresési parancsikonokat nem sikerült betölteni.", + "The search shortcuts could not be saved." : "A keresési parancsikonokat nem sikerült menteni.", + "Search {name} for {query}" : "Keresés itt: {name}, erre: {query}", + "Type what to search for in {name}" : "Írd be, mit keresel itt: {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/hu.json b/l10n/hu.json index 73771ec3f..53b1313a3 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Betűrendben", "Most used": "Leggyakrabban használt", "Last used": "Legutóbb használt", - "At random": "Véletlenszerűen" + "At random": "Véletlenszerűen", + "Search shortcuts": "Keresési parancsikonok", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Az előtaggal kezdődő keresés, például !t printer, közvetlenül az adott oldalra visz egy új lapon. Ha ? jelet írsz a keresőmezőbe, megjelennek a parancsikonok.", + "Prefix": "Előtag", + "Address with {query}": "Cím {query} jelöléssel", + "No search shortcuts yet.": "Még nincsenek keresési parancsikonok.", + "Search shortcuts saved.": "Keresési parancsikonok mentve.", + "Add shortcut": "Parancsikon hozzáadása", + "Save search shortcuts": "Keresési parancsikonok mentése", + "The search shortcuts could not be loaded.": "A keresési parancsikonokat nem sikerült betölteni.", + "The search shortcuts could not be saved.": "A keresési parancsikonokat nem sikerült menteni.", + "Search {name} for {query}": "Keresés itt: {name}, erre: {query}", + "Type what to search for in {name}": "Írd be, mit keresel itt: {name}" } } diff --git a/l10n/is.js b/l10n/is.js index 90bb6f0a6..4f90573d0 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Í stafrófsröð", "Most used" : "Mest notað", "Last used" : "Síðast notað", - "At random" : "Af handahófi" + "At random" : "Af handahófi", + "Search shortcuts" : "Leitarflýtileiðir", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Leit sem byrjar á forskeyti, eins og !t printer, fer beint á þá síðu í nýjum flipa. Sláðu inn ? í leitarreit til að sjá flýtileiðirnar.", + "Prefix" : "Forskeyti", + "Address with {query}" : "Vefslóð með {query}", + "No search shortcuts yet." : "Engar leitarflýtileiðir enn.", + "Search shortcuts saved." : "Leitarflýtileiðir vistaðar.", + "Add shortcut" : "Bæta við flýtileið", + "Save search shortcuts" : "Vista leitarflýtileiðir", + "The search shortcuts could not be loaded." : "Ekki tókst að hlaða inn leitarflýtileiðum.", + "The search shortcuts could not be saved." : "Ekki tókst að vista leitarflýtileiðirnar.", + "Search {name} for {query}" : "Leita í {name} að {query}", + "Type what to search for in {name}" : "Sláðu inn hvað á að leita að í {name}" }, "nplurals=2; plural=(n%10!=1 || n%100==11);" ); diff --git a/l10n/is.json b/l10n/is.json index 3b278cd6e..ff9642953 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Í stafrófsröð", "Most used": "Mest notað", "Last used": "Síðast notað", - "At random": "Af handahófi" + "At random": "Af handahófi", + "Search shortcuts": "Leitarflýtileiðir", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Leit sem byrjar á forskeyti, eins og !t printer, fer beint á þá síðu í nýjum flipa. Sláðu inn ? í leitarreit til að sjá flýtileiðirnar.", + "Prefix": "Forskeyti", + "Address with {query}": "Vefslóð með {query}", + "No search shortcuts yet.": "Engar leitarflýtileiðir enn.", + "Search shortcuts saved.": "Leitarflýtileiðir vistaðar.", + "Add shortcut": "Bæta við flýtileið", + "Save search shortcuts": "Vista leitarflýtileiðir", + "The search shortcuts could not be loaded.": "Ekki tókst að hlaða inn leitarflýtileiðum.", + "The search shortcuts could not be saved.": "Ekki tókst að vista leitarflýtileiðirnar.", + "Search {name} for {query}": "Leita í {name} að {query}", + "Type what to search for in {name}": "Sláðu inn hvað á að leita að í {name}" } } diff --git a/l10n/it.js b/l10n/it.js index 084ad08ea..29c3b4ec4 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "In ordine alfabetico", "Most used" : "Più usati", "Last used" : "Ultimo utilizzo", - "At random" : "A caso" + "At random" : "A caso", + "Search shortcuts" : "Scorciatoie di ricerca", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Una ricerca che inizia con un prefisso, come !t printer, va direttamente a quel sito in una nuova scheda. Digitando ? in una casella di ricerca si vedono le scorciatoie.", + "Prefix" : "Prefisso", + "Address with {query}" : "Indirizzo con {query}", + "No search shortcuts yet." : "Nessuna scorciatoia di ricerca per ora.", + "Search shortcuts saved." : "Scorciatoie di ricerca salvate.", + "Add shortcut" : "Aggiungi scorciatoia", + "Save search shortcuts" : "Salva scorciatoie di ricerca", + "The search shortcuts could not be loaded." : "Non è stato possibile caricare le scorciatoie di ricerca.", + "The search shortcuts could not be saved." : "Non è stato possibile salvare le scorciatoie di ricerca.", + "Search {name} for {query}" : "Cerca {query} in {name}", + "Type what to search for in {name}" : "Scrivi cosa cercare in {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/it.json b/l10n/it.json index 0bf896a8c..a2f5b95cd 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -1299,6 +1299,18 @@ "Alphabetically": "In ordine alfabetico", "Most used": "Più usati", "Last used": "Ultimo utilizzo", - "At random": "A caso" + "At random": "A caso", + "Search shortcuts": "Scorciatoie di ricerca", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Una ricerca che inizia con un prefisso, come !t printer, va direttamente a quel sito in una nuova scheda. Digitando ? in una casella di ricerca si vedono le scorciatoie.", + "Prefix": "Prefisso", + "Address with {query}": "Indirizzo con {query}", + "No search shortcuts yet.": "Nessuna scorciatoia di ricerca per ora.", + "Search shortcuts saved.": "Scorciatoie di ricerca salvate.", + "Add shortcut": "Aggiungi scorciatoia", + "Save search shortcuts": "Salva scorciatoie di ricerca", + "The search shortcuts could not be loaded.": "Non è stato possibile caricare le scorciatoie di ricerca.", + "The search shortcuts could not be saved.": "Non è stato possibile salvare le scorciatoie di ricerca.", + "Search {name} for {query}": "Cerca {query} in {name}", + "Type what to search for in {name}": "Scrivi cosa cercare in {name}" } } diff --git a/l10n/lb.js b/l10n/lb.js index 57ee7d502..5c290ec2d 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -1323,7 +1323,19 @@ OC.L10N.register( "Alphabetically" : "Alphabetesch", "Most used" : "Am meeschte benotzt", "Last used" : "Lescht benotzt", - "At random" : "Zoufälleg" + "At random" : "Zoufälleg", + "Search shortcuts" : "Sich-Ofkierzungen", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Eng Sich, déi mat engem Präfix ufänkt, wéi !t printer, geet direkt op dee Site an engem neien Tab. Mat ? am Sichfeld gesäis du d'Ofkierzungen.", + "Prefix" : "Präfix", + "Address with {query}" : "Adress mat {query}", + "No search shortcuts yet." : "Nach keng Sich-Ofkierzungen.", + "Search shortcuts saved." : "Sich-Ofkierzunge gespäichert.", + "Add shortcut" : "Ofkierzung derbäisetzen", + "Save search shortcuts" : "Sich-Ofkierzunge späicheren", + "The search shortcuts could not be loaded." : "D'Sich-Ofkierzunge konnten net geluede ginn.", + "The search shortcuts could not be saved." : "D'Sich-Ofkierzunge konnten net gespäichert ginn.", + "Search {name} for {query}" : "A {name} no {query} sichen", + "Type what to search for in {name}" : "Gëff an, wat an {name} gesicht gëtt" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/lb.json b/l10n/lb.json index 530df9f16..fc3260785 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alphabetesch", "Most used": "Am meeschte benotzt", "Last used": "Lescht benotzt", - "At random": "Zoufälleg" + "At random": "Zoufälleg", + "Search shortcuts": "Sich-Ofkierzungen", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Eng Sich, déi mat engem Präfix ufänkt, wéi !t printer, geet direkt op dee Site an engem neien Tab. Mat ? am Sichfeld gesäis du d'Ofkierzungen.", + "Prefix": "Präfix", + "Address with {query}": "Adress mat {query}", + "No search shortcuts yet.": "Nach keng Sich-Ofkierzungen.", + "Search shortcuts saved.": "Sich-Ofkierzunge gespäichert.", + "Add shortcut": "Ofkierzung derbäisetzen", + "Save search shortcuts": "Sich-Ofkierzunge späicheren", + "The search shortcuts could not be loaded.": "D'Sich-Ofkierzunge konnten net geluede ginn.", + "The search shortcuts could not be saved.": "D'Sich-Ofkierzunge konnten net gespäichert ginn.", + "Search {name} for {query}": "A {name} no {query} sichen", + "Type what to search for in {name}": "Gëff an, wat an {name} gesicht gëtt" } } diff --git a/l10n/lt.js b/l10n/lt.js index b9f290070..6cae31274 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Abėcėlės tvarka", "Most used" : "Dažniausiai naudojami", "Last used" : "Paskutiniai naudoti", - "At random" : "Atsitiktinai" + "At random" : "Atsitiktinai", + "Search shortcuts" : "Paieškos nuorodos", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Paieška, prasidedanti priešdėliu, pvz., !t printer, iškart atveria tą svetainę naujame skirtuke. Įvedę ? paieškos laukelyje pamatysite nuorodas.", + "Prefix" : "Priešdėlis", + "Address with {query}" : "Adresas su {query}", + "No search shortcuts yet." : "Paieškos nuorodų dar nėra.", + "Search shortcuts saved." : "Paieškos nuorodos išsaugotos.", + "Add shortcut" : "Pridėti nuorodą", + "Save search shortcuts" : "Išsaugoti paieškos nuorodas", + "The search shortcuts could not be loaded." : "Nepavyko įkelti paieškos nuorodų.", + "The search shortcuts could not be saved." : "Nepavyko išsaugoti paieškos nuorodų.", + "Search {name} for {query}" : "Ieškoti {name}: {query}", + "Type what to search for in {name}" : "Įveskite, ko ieškoti {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/lt.json b/l10n/lt.json index 6a77144f8..a74b6a426 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Abėcėlės tvarka", "Most used": "Dažniausiai naudojami", "Last used": "Paskutiniai naudoti", - "At random": "Atsitiktinai" + "At random": "Atsitiktinai", + "Search shortcuts": "Paieškos nuorodos", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Paieška, prasidedanti priešdėliu, pvz., !t printer, iškart atveria tą svetainę naujame skirtuke. Įvedę ? paieškos laukelyje pamatysite nuorodas.", + "Prefix": "Priešdėlis", + "Address with {query}": "Adresas su {query}", + "No search shortcuts yet.": "Paieškos nuorodų dar nėra.", + "Search shortcuts saved.": "Paieškos nuorodos išsaugotos.", + "Add shortcut": "Pridėti nuorodą", + "Save search shortcuts": "Išsaugoti paieškos nuorodas", + "The search shortcuts could not be loaded.": "Nepavyko įkelti paieškos nuorodų.", + "The search shortcuts could not be saved.": "Nepavyko išsaugoti paieškos nuorodų.", + "Search {name} for {query}": "Ieškoti {name}: {query}", + "Type what to search for in {name}": "Įveskite, ko ieškoti {name}" } } diff --git a/l10n/lv.js b/l10n/lv.js index 375685874..f2fb40691 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabētiski", "Most used" : "Visbiežāk lietotie", "Last used" : "Pēdējie lietotie", - "At random" : "Nejauši" + "At random" : "Nejauši", + "Search shortcuts" : "Meklēšanas saīsnes", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Meklēšana, kas sākas ar prefiksu, piemēram, !t printer, uzreiz atver šo vietni jaunā cilnē. Ierakstot ? meklēšanas lodziņā, redzamas saīsnes.", + "Prefix" : "Prefikss", + "Address with {query}" : "Adrese ar {query}", + "No search shortcuts yet." : "Meklēšanas saīsņu vēl nav.", + "Search shortcuts saved." : "Meklēšanas saīsnes saglabātas.", + "Add shortcut" : "Pievienot saīsni", + "Save search shortcuts" : "Saglabāt meklēšanas saīsnes", + "The search shortcuts could not be loaded." : "Neizdevās ielādēt meklēšanas saīsnes.", + "The search shortcuts could not be saved." : "Neizdevās saglabāt meklēšanas saīsnes.", + "Search {name} for {query}" : "Meklēt {name}: {query}", + "Type what to search for in {name}" : "Ierakstiet, ko meklēt {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" ); diff --git a/l10n/lv.json b/l10n/lv.json index ddc13ea1d..aa43ec05c 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Alfabētiski", "Most used": "Visbiežāk lietotie", "Last used": "Pēdējie lietotie", - "At random": "Nejauši" + "At random": "Nejauši", + "Search shortcuts": "Meklēšanas saīsnes", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Meklēšana, kas sākas ar prefiksu, piemēram, !t printer, uzreiz atver šo vietni jaunā cilnē. Ierakstot ? meklēšanas lodziņā, redzamas saīsnes.", + "Prefix": "Prefikss", + "Address with {query}": "Adrese ar {query}", + "No search shortcuts yet.": "Meklēšanas saīsņu vēl nav.", + "Search shortcuts saved.": "Meklēšanas saīsnes saglabātas.", + "Add shortcut": "Pievienot saīsni", + "Save search shortcuts": "Saglabāt meklēšanas saīsnes", + "The search shortcuts could not be loaded.": "Neizdevās ielādēt meklēšanas saīsnes.", + "The search shortcuts could not be saved.": "Neizdevās saglabāt meklēšanas saīsnes.", + "Search {name} for {query}": "Meklēt {name}: {query}", + "Type what to search for in {name}": "Ierakstiet, ko meklēt {name}" } } diff --git a/l10n/mk.js b/l10n/mk.js index 1309b932d..7362e3b7c 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -1323,7 +1323,19 @@ OC.L10N.register( "Alphabetically" : "Азбучно", "Most used" : "Најкористени", "Last used" : "Последно користени", - "At random" : "По случаен избор" + "At random" : "По случаен избор", + "Search shortcuts" : "Кратенки за пребарување", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Пребарување што почнува со префикс, на пр. !t printer, оди директно на таа страница во ново јазиче. Со внесување ? во полето за пребарување се прикажуваат кратенките.", + "Prefix" : "Префикс", + "Address with {query}" : "Адреса со {query}", + "No search shortcuts yet." : "Сè уште нема кратенки за пребарување.", + "Search shortcuts saved." : "Кратенките за пребарување се зачувани.", + "Add shortcut" : "Додај кратенка", + "Save search shortcuts" : "Зачувај кратенки за пребарување", + "The search shortcuts could not be loaded." : "Кратенките за пребарување не можеа да се вчитаат.", + "The search shortcuts could not be saved." : "Кратенките за пребарување не можеа да се зачуваат.", + "Search {name} for {query}" : "Пребарај во {name} за {query}", + "Type what to search for in {name}" : "Внесете што да се бара во {name}" }, "nplurals=2; plural=(n%10==1 && n%100!=11 ? 0 : 1);" ); diff --git a/l10n/mk.json b/l10n/mk.json index ab48acee8..fb1125f6f 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Азбучно", "Most used": "Најкористени", "Last used": "Последно користени", - "At random": "По случаен избор" + "At random": "По случаен избор", + "Search shortcuts": "Кратенки за пребарување", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Пребарување што почнува со префикс, на пр. !t printer, оди директно на таа страница во ново јазиче. Со внесување ? во полето за пребарување се прикажуваат кратенките.", + "Prefix": "Префикс", + "Address with {query}": "Адреса со {query}", + "No search shortcuts yet.": "Сè уште нема кратенки за пребарување.", + "Search shortcuts saved.": "Кратенките за пребарување се зачувани.", + "Add shortcut": "Додај кратенка", + "Save search shortcuts": "Зачувај кратенки за пребарување", + "The search shortcuts could not be loaded.": "Кратенките за пребарување не можеа да се вчитаат.", + "The search shortcuts could not be saved.": "Кратенките за пребарување не можеа да се зачуваат.", + "Search {name} for {query}": "Пребарај во {name} за {query}", + "Type what to search for in {name}": "Внесете што да се бара во {name}" } } diff --git a/l10n/mt.js b/l10n/mt.js index efeb4b39b..27d0a5338 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetikament", "Most used" : "L-aktar użati", "Last used" : "L-aħħar użati", - "At random" : "B'mod każwali" + "At random" : "B'mod każwali", + "Search shortcuts" : "Shortcuts tat-tfittxija", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Tfittxija li tibda bi prefiss, bħal !t printer, tmur dritt għal dak is-sit f'tab ġdida. Ikteb ? f'kaxxa tat-tfittxija biex tara l-shortcuts.", + "Prefix" : "Prefiss", + "Address with {query}" : "Indirizz b'{query}", + "No search shortcuts yet." : "Għad m'hemmx shortcuts tat-tfittxija.", + "Search shortcuts saved." : "Il-shortcuts tat-tfittxija ġew salvati.", + "Add shortcut" : "Żid shortcut", + "Save search shortcuts" : "Issejvja l-shortcuts tat-tfittxija", + "The search shortcuts could not be loaded." : "Il-shortcuts tat-tfittxija ma setgħux jitgħabbew.", + "The search shortcuts could not be saved." : "Il-shortcuts tat-tfittxija ma setgħux jiġu salvati.", + "Search {name} for {query}" : "Fittex {name} għal {query}", + "Type what to search for in {name}" : "Ikteb x'tixtieq tfittex f'{name}" }, "nplurals=4; plural=(n==1 ? 0 : n==0 || (n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20) ? 2 : 3);" ); diff --git a/l10n/mt.json b/l10n/mt.json index 65e7e70ea..0b718764f 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -1301,6 +1301,18 @@ "Alphabetically": "Alfabetikament", "Most used": "L-aktar użati", "Last used": "L-aħħar użati", - "At random": "B'mod każwali" + "At random": "B'mod każwali", + "Search shortcuts": "Shortcuts tat-tfittxija", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Tfittxija li tibda bi prefiss, bħal !t printer, tmur dritt għal dak is-sit f'tab ġdida. Ikteb ? f'kaxxa tat-tfittxija biex tara l-shortcuts.", + "Prefix": "Prefiss", + "Address with {query}": "Indirizz b'{query}", + "No search shortcuts yet.": "Għad m'hemmx shortcuts tat-tfittxija.", + "Search shortcuts saved.": "Il-shortcuts tat-tfittxija ġew salvati.", + "Add shortcut": "Żid shortcut", + "Save search shortcuts": "Issejvja l-shortcuts tat-tfittxija", + "The search shortcuts could not be loaded.": "Il-shortcuts tat-tfittxija ma setgħux jitgħabbew.", + "The search shortcuts could not be saved.": "Il-shortcuts tat-tfittxija ma setgħux jiġu salvati.", + "Search {name} for {query}": "Fittex {name} għal {query}", + "Type what to search for in {name}": "Ikteb x'tixtieq tfittex f'{name}" } } diff --git a/l10n/nb.js b/l10n/nb.js index cadeff1a3..99e2066b9 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetisk", "Most used" : "Mest brukt", "Last used" : "Sist brukt", - "At random" : "Tilfeldig" + "At random" : "Tilfeldig", + "Search shortcuts" : "Søkesnarveier", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Et søk som starter med et prefiks, for eksempel !t printer, går rett til det nettstedet i en ny fane. Skriv ? i et søkefelt for å se snarveiene.", + "Prefix" : "Prefiks", + "Address with {query}" : "Adresse med {query}", + "No search shortcuts yet." : "Ingen søkesnarveier ennå.", + "Search shortcuts saved." : "Søkesnarveiene er lagret.", + "Add shortcut" : "Legg til snarvei", + "Save search shortcuts" : "Lagre søkesnarveier", + "The search shortcuts could not be loaded." : "Søkesnarveiene kunne ikke lastes inn.", + "The search shortcuts could not be saved." : "Søkesnarveiene kunne ikke lagres.", + "Search {name} for {query}" : "Søk i {name} etter {query}", + "Type what to search for in {name}" : "Skriv hva du vil søke etter i {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nb.json b/l10n/nb.json index 8059022ed..e21520921 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabetisk", "Most used": "Mest brukt", "Last used": "Sist brukt", - "At random": "Tilfeldig" + "At random": "Tilfeldig", + "Search shortcuts": "Søkesnarveier", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Et søk som starter med et prefiks, for eksempel !t printer, går rett til det nettstedet i en ny fane. Skriv ? i et søkefelt for å se snarveiene.", + "Prefix": "Prefiks", + "Address with {query}": "Adresse med {query}", + "No search shortcuts yet.": "Ingen søkesnarveier ennå.", + "Search shortcuts saved.": "Søkesnarveiene er lagret.", + "Add shortcut": "Legg til snarvei", + "Save search shortcuts": "Lagre søkesnarveier", + "The search shortcuts could not be loaded.": "Søkesnarveiene kunne ikke lastes inn.", + "The search shortcuts could not be saved.": "Søkesnarveiene kunne ikke lagres.", + "Search {name} for {query}": "Søk i {name} etter {query}", + "Type what to search for in {name}": "Skriv hva du vil søke etter i {name}" } } diff --git a/l10n/nl.js b/l10n/nl.js index 8648eb478..106de86b2 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -1382,7 +1382,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetisch", "Most used" : "Meest gebruikt", "Last used" : "Laatst gebruikt", - "At random" : "Willekeurig" + "At random" : "Willekeurig", + "Search shortcuts" : "Zoeksnelkoppelingen", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Een zoekopdracht die begint met een voorvoegsel, zoals !t printer, gaat meteen naar die site in een nieuw tabblad. Typ ? in een zoekvak om de snelkoppelingen te zien.", + "Prefix" : "Voorvoegsel", + "Address with {query}" : "Adres met {query}", + "No search shortcuts yet." : "Nog geen zoeksnelkoppelingen.", + "Search shortcuts saved." : "Zoeksnelkoppelingen opgeslagen.", + "Add shortcut" : "Snelkoppeling toevoegen", + "Save search shortcuts" : "Zoeksnelkoppelingen opslaan", + "The search shortcuts could not be loaded." : "De zoeksnelkoppelingen konden niet worden geladen.", + "The search shortcuts could not be saved." : "De zoeksnelkoppelingen konden niet worden opgeslagen.", + "Search {name} for {query}" : "Zoek in {name} naar {query}", + "Type what to search for in {name}" : "Typ waarnaar je in {name} wilt zoeken" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nl.json b/l10n/nl.json index e774d1823..151627e67 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -1359,6 +1359,18 @@ "Alphabetically": "Alfabetisch", "Most used": "Meest gebruikt", "Last used": "Laatst gebruikt", - "At random": "Willekeurig" + "At random": "Willekeurig", + "Search shortcuts": "Zoeksnelkoppelingen", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Een zoekopdracht die begint met een voorvoegsel, zoals !t printer, gaat meteen naar die site in een nieuw tabblad. Typ ? in een zoekvak om de snelkoppelingen te zien.", + "Prefix": "Voorvoegsel", + "Address with {query}": "Adres met {query}", + "No search shortcuts yet.": "Nog geen zoeksnelkoppelingen.", + "Search shortcuts saved.": "Zoeksnelkoppelingen opgeslagen.", + "Add shortcut": "Snelkoppeling toevoegen", + "Save search shortcuts": "Zoeksnelkoppelingen opslaan", + "The search shortcuts could not be loaded.": "De zoeksnelkoppelingen konden niet worden geladen.", + "The search shortcuts could not be saved.": "De zoeksnelkoppelingen konden niet worden opgeslagen.", + "Search {name} for {query}": "Zoek in {name} naar {query}", + "Type what to search for in {name}": "Typ waarnaar je in {name} wilt zoeken" } } diff --git a/l10n/pl.js b/l10n/pl.js index 285dce17a..e92140d87 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetycznie", "Most used" : "Najczęściej używane", "Last used" : "Ostatnio używane", - "At random" : "Losowo" + "At random" : "Losowo", + "Search shortcuts" : "Skróty wyszukiwania", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Wyszukiwanie zaczynające się od prefiksu, np. !t printer, przechodzi od razu do tej witryny w nowej karcie. Wpisanie ? w polu wyszukiwania pokazuje listę skrótów.", + "Prefix" : "Prefiks", + "Address with {query}" : "Adres z {query}", + "No search shortcuts yet." : "Brak skrótów wyszukiwania.", + "Search shortcuts saved." : "Skróty wyszukiwania zapisane.", + "Add shortcut" : "Dodaj skrót", + "Save search shortcuts" : "Zapisz skróty wyszukiwania", + "The search shortcuts could not be loaded." : "Nie udało się wczytać skrótów wyszukiwania.", + "The search shortcuts could not be saved." : "Nie udało się zapisać skrótów wyszukiwania.", + "Search {name} for {query}" : "Szukaj w {name}: {query}", + "Type what to search for in {name}" : "Wpisz, czego szukać w {name}" }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/pl.json b/l10n/pl.json index e82900981..9c36922cd 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Alfabetycznie", "Most used": "Najczęściej używane", "Last used": "Ostatnio używane", - "At random": "Losowo" + "At random": "Losowo", + "Search shortcuts": "Skróty wyszukiwania", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Wyszukiwanie zaczynające się od prefiksu, np. !t printer, przechodzi od razu do tej witryny w nowej karcie. Wpisanie ? w polu wyszukiwania pokazuje listę skrótów.", + "Prefix": "Prefiks", + "Address with {query}": "Adres z {query}", + "No search shortcuts yet.": "Brak skrótów wyszukiwania.", + "Search shortcuts saved.": "Skróty wyszukiwania zapisane.", + "Add shortcut": "Dodaj skrót", + "Save search shortcuts": "Zapisz skróty wyszukiwania", + "The search shortcuts could not be loaded.": "Nie udało się wczytać skrótów wyszukiwania.", + "The search shortcuts could not be saved.": "Nie udało się zapisać skrótów wyszukiwania.", + "Search {name} for {query}": "Szukaj w {name}: {query}", + "Type what to search for in {name}": "Wpisz, czego szukać w {name}" } } diff --git a/l10n/pt.js b/l10n/pt.js index e515e3e67..015fafad2 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabeticamente", "Most used" : "Mais usados", "Last used" : "Último uso", - "At random" : "Aleatoriamente" + "At random" : "Aleatoriamente", + "Search shortcuts" : "Atalhos de pesquisa", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Uma pesquisa que começa com um prefixo, como !t printer, vai diretamente para esse site num novo separador. Escrever ? numa caixa de pesquisa mostra os atalhos.", + "Prefix" : "Prefixo", + "Address with {query}" : "Endereço com {query}", + "No search shortcuts yet." : "Ainda não há atalhos de pesquisa.", + "Search shortcuts saved." : "Atalhos de pesquisa guardados.", + "Add shortcut" : "Adicionar atalho", + "Save search shortcuts" : "Guardar atalhos de pesquisa", + "The search shortcuts could not be loaded." : "Não foi possível carregar os atalhos de pesquisa.", + "The search shortcuts could not be saved." : "Não foi possível guardar os atalhos de pesquisa.", + "Search {name} for {query}" : "Pesquisar {query} em {name}", + "Type what to search for in {name}" : "Escreva o que pesquisar em {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/pt.json b/l10n/pt.json index 90c186688..8d6217dfd 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabeticamente", "Most used": "Mais usados", "Last used": "Último uso", - "At random": "Aleatoriamente" + "At random": "Aleatoriamente", + "Search shortcuts": "Atalhos de pesquisa", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Uma pesquisa que começa com um prefixo, como !t printer, vai diretamente para esse site num novo separador. Escrever ? numa caixa de pesquisa mostra os atalhos.", + "Prefix": "Prefixo", + "Address with {query}": "Endereço com {query}", + "No search shortcuts yet.": "Ainda não há atalhos de pesquisa.", + "Search shortcuts saved.": "Atalhos de pesquisa guardados.", + "Add shortcut": "Adicionar atalho", + "Save search shortcuts": "Guardar atalhos de pesquisa", + "The search shortcuts could not be loaded.": "Não foi possível carregar os atalhos de pesquisa.", + "The search shortcuts could not be saved.": "Não foi possível guardar os atalhos de pesquisa.", + "Search {name} for {query}": "Pesquisar {query} em {name}", + "Type what to search for in {name}": "Escreva o que pesquisar em {name}" } } diff --git a/l10n/rm.js b/l10n/rm.js index 9453ed16a..f77db960b 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabeticamain", "Most used" : "Il pli savens duvrà", "Last used" : "Duvrà l'ultim", - "At random" : "A chaschun" + "At random" : "A chaschun", + "Search shortcuts" : "Scursanidas da tschertga", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Ina tschertga che cumenza cun in prefix, sco !t printer, va directamain a quella pagina en in nov tab. Cun ? en in champ da tschertga vesas las scursanidas.", + "Prefix" : "Prefix", + "Address with {query}" : "Adressa cun {query}", + "No search shortcuts yet." : "Anc naginas scursanidas da tschertga.", + "Search shortcuts saved." : "Scursanidas da tschertga memorisadas.", + "Add shortcut" : "Agiuntar ina scursanida", + "Save search shortcuts" : "Memorisar las scursanidas da tschertga", + "The search shortcuts could not be loaded." : "I n'è betg reussì da chargiar las scursanidas da tschertga.", + "The search shortcuts could not be saved." : "I n'è betg reussì da memorisar las scursanidas da tschertga.", + "Search {name} for {query}" : "Tschertgar en {name} suenter {query}", + "Type what to search for in {name}" : "Endatescha tge tschertgar en {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/rm.json b/l10n/rm.json index 566dd6a70..2b6876037 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabeticamain", "Most used": "Il pli savens duvrà", "Last used": "Duvrà l'ultim", - "At random": "A chaschun" + "At random": "A chaschun", + "Search shortcuts": "Scursanidas da tschertga", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Ina tschertga che cumenza cun in prefix, sco !t printer, va directamain a quella pagina en in nov tab. Cun ? en in champ da tschertga vesas las scursanidas.", + "Prefix": "Prefix", + "Address with {query}": "Adressa cun {query}", + "No search shortcuts yet.": "Anc naginas scursanidas da tschertga.", + "Search shortcuts saved.": "Scursanidas da tschertga memorisadas.", + "Add shortcut": "Agiuntar ina scursanida", + "Save search shortcuts": "Memorisar las scursanidas da tschertga", + "The search shortcuts could not be loaded.": "I n'è betg reussì da chargiar las scursanidas da tschertga.", + "The search shortcuts could not be saved.": "I n'è betg reussì da memorisar las scursanidas da tschertga.", + "Search {name} for {query}": "Tschertgar en {name} suenter {query}", + "Type what to search for in {name}": "Endatescha tge tschertgar en {name}" } } diff --git a/l10n/ro.js b/l10n/ro.js index 358d66e1a..592be2a0f 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetic", "Most used" : "Cele mai folosite", "Last used" : "Ultima utilizare", - "At random" : "Aleatoriu" + "At random" : "Aleatoriu", + "Search shortcuts" : "Scurtături de căutare", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "O căutare care începe cu un prefix, de exemplu !t printer, merge direct pe acel site într-o filă nouă. Tastând ? într-o casetă de căutare vezi scurtăturile.", + "Prefix" : "Prefix", + "Address with {query}" : "Adresă cu {query}", + "No search shortcuts yet." : "Încă nu există scurtături de căutare.", + "Search shortcuts saved." : "Scurtăturile de căutare au fost salvate.", + "Add shortcut" : "Adaugă scurtătură", + "Save search shortcuts" : "Salvează scurtăturile de căutare", + "The search shortcuts could not be loaded." : "Scurtăturile de căutare nu au putut fi încărcate.", + "The search shortcuts could not be saved." : "Scurtăturile de căutare nu au putut fi salvate.", + "Search {name} for {query}" : "Caută în {name} după {query}", + "Type what to search for in {name}" : "Scrie ce să cauți în {name}" }, "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);" ); diff --git a/l10n/ro.json b/l10n/ro.json index 4ce56bcf0..1df78b4ee 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Alfabetic", "Most used": "Cele mai folosite", "Last used": "Ultima utilizare", - "At random": "Aleatoriu" + "At random": "Aleatoriu", + "Search shortcuts": "Scurtături de căutare", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "O căutare care începe cu un prefix, de exemplu !t printer, merge direct pe acel site într-o filă nouă. Tastând ? într-o casetă de căutare vezi scurtăturile.", + "Prefix": "Prefix", + "Address with {query}": "Adresă cu {query}", + "No search shortcuts yet.": "Încă nu există scurtături de căutare.", + "Search shortcuts saved.": "Scurtăturile de căutare au fost salvate.", + "Add shortcut": "Adaugă scurtătură", + "Save search shortcuts": "Salvează scurtăturile de căutare", + "The search shortcuts could not be loaded.": "Scurtăturile de căutare nu au putut fi încărcate.", + "The search shortcuts could not be saved.": "Scurtăturile de căutare nu au putut fi salvate.", + "Search {name} for {query}": "Caută în {name} după {query}", + "Type what to search for in {name}": "Scrie ce să cauți în {name}" } } diff --git a/l10n/ru.js b/l10n/ru.js index 215c42ce6..114a48973 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "По алфавиту", "Most used" : "Самые используемые", "Last used" : "Последние использованные", - "At random" : "Случайно" + "At random" : "Случайно", + "Search shortcuts" : "Ярлыки поиска", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Поиск, начинающийся с префикса, например !t printer, сразу открывает этот сайт в новой вкладке. Введите ? в поле поиска, чтобы увидеть ярлыки.", + "Prefix" : "Префикс", + "Address with {query}" : "Адрес с {query}", + "No search shortcuts yet." : "Ярлыков поиска пока нет.", + "Search shortcuts saved." : "Ярлыки поиска сохранены.", + "Add shortcut" : "Добавить ярлык", + "Save search shortcuts" : "Сохранить ярлыки поиска", + "The search shortcuts could not be loaded." : "Не удалось загрузить ярлыки поиска.", + "The search shortcuts could not be saved." : "Не удалось сохранить ярлыки поиска.", + "Search {name} for {query}" : "Искать в {name}: {query}", + "Type what to search for in {name}" : "Введите, что искать в {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/ru.json b/l10n/ru.json index 752062dab..fd37f3fdd 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -1300,6 +1300,18 @@ "Alphabetically": "По алфавиту", "Most used": "Самые используемые", "Last used": "Последние использованные", - "At random": "Случайно" + "At random": "Случайно", + "Search shortcuts": "Ярлыки поиска", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Поиск, начинающийся с префикса, например !t printer, сразу открывает этот сайт в новой вкладке. Введите ? в поле поиска, чтобы увидеть ярлыки.", + "Prefix": "Префикс", + "Address with {query}": "Адрес с {query}", + "No search shortcuts yet.": "Ярлыков поиска пока нет.", + "Search shortcuts saved.": "Ярлыки поиска сохранены.", + "Add shortcut": "Добавить ярлык", + "Save search shortcuts": "Сохранить ярлыки поиска", + "The search shortcuts could not be loaded.": "Не удалось загрузить ярлыки поиска.", + "The search shortcuts could not be saved.": "Не удалось сохранить ярлыки поиска.", + "Search {name} for {query}": "Искать в {name}: {query}", + "Type what to search for in {name}": "Введите, что искать в {name}" } } diff --git a/l10n/sk.js b/l10n/sk.js index dc9930564..abf4fb4c6 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Abecedne", "Most used" : "Najpoužívanejšie", "Last used" : "Naposledy použité", - "At random" : "Náhodne" + "At random" : "Náhodne", + "Search shortcuts" : "Skratky vyhľadávania", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Vyhľadávanie, ktoré začína predponou, napríklad !t printer, prejde priamo na danú stránku v novej karte. Napísaním ? do vyhľadávacieho poľa zobrazíte skratky.", + "Prefix" : "Predpona", + "Address with {query}" : "Adresa s {query}", + "No search shortcuts yet." : "Zatiaľ žiadne skratky vyhľadávania.", + "Search shortcuts saved." : "Skratky vyhľadávania sú uložené.", + "Add shortcut" : "Pridať skratku", + "Save search shortcuts" : "Uložiť skratky vyhľadávania", + "The search shortcuts could not be loaded." : "Skratky vyhľadávania sa nepodarilo načítať.", + "The search shortcuts could not be saved." : "Skratky vyhľadávania sa nepodarilo uložiť.", + "Search {name} for {query}" : "Hľadať v {name}: {query}", + "Type what to search for in {name}" : "Napíšte, čo hľadať v {name}" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/sk.json b/l10n/sk.json index f4ad5b771..d641da3ee 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Abecedne", "Most used": "Najpoužívanejšie", "Last used": "Naposledy použité", - "At random": "Náhodne" + "At random": "Náhodne", + "Search shortcuts": "Skratky vyhľadávania", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Vyhľadávanie, ktoré začína predponou, napríklad !t printer, prejde priamo na danú stránku v novej karte. Napísaním ? do vyhľadávacieho poľa zobrazíte skratky.", + "Prefix": "Predpona", + "Address with {query}": "Adresa s {query}", + "No search shortcuts yet.": "Zatiaľ žiadne skratky vyhľadávania.", + "Search shortcuts saved.": "Skratky vyhľadávania sú uložené.", + "Add shortcut": "Pridať skratku", + "Save search shortcuts": "Uložiť skratky vyhľadávania", + "The search shortcuts could not be loaded.": "Skratky vyhľadávania sa nepodarilo načítať.", + "The search shortcuts could not be saved.": "Skratky vyhľadávania sa nepodarilo uložiť.", + "Search {name} for {query}": "Hľadať v {name}: {query}", + "Type what to search for in {name}": "Napíšte, čo hľadať v {name}" } } diff --git a/l10n/sl.js b/l10n/sl.js index aae665454..7b5c86ecd 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Po abecedi", "Most used" : "Najpogosteje uporabljeno", "Last used" : "Nazadnje uporabljeno", - "At random" : "Naključno" + "At random" : "Naključno", + "Search shortcuts" : "Bližnjice iskanja", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Iskanje, ki se začne s predpono, na primer !t printer, gre neposredno na to spletno mesto v novem zavihku. Če v iskalno polje vnesete ?, se prikažejo bližnjice.", + "Prefix" : "Predpona", + "Address with {query}" : "Naslov z {query}", + "No search shortcuts yet." : "Bližnjic iskanja še ni.", + "Search shortcuts saved." : "Bližnjice iskanja so shranjene.", + "Add shortcut" : "Dodaj bližnjico", + "Save search shortcuts" : "Shrani bližnjice iskanja", + "The search shortcuts could not be loaded." : "Bližnjic iskanja ni bilo mogoče naložiti.", + "The search shortcuts could not be saved." : "Bližnjic iskanja ni bilo mogoče shraniti.", + "Search {name} for {query}" : "Išči v {name}: {query}", + "Type what to search for in {name}" : "Vnesite, kaj iskati v {name}" }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" ); diff --git a/l10n/sl.json b/l10n/sl.json index a46e1bfe9..939bb84b4 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -1301,6 +1301,18 @@ "Alphabetically": "Po abecedi", "Most used": "Najpogosteje uporabljeno", "Last used": "Nazadnje uporabljeno", - "At random": "Naključno" + "At random": "Naključno", + "Search shortcuts": "Bližnjice iskanja", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Iskanje, ki se začne s predpono, na primer !t printer, gre neposredno na to spletno mesto v novem zavihku. Če v iskalno polje vnesete ?, se prikažejo bližnjice.", + "Prefix": "Predpona", + "Address with {query}": "Naslov z {query}", + "No search shortcuts yet.": "Bližnjic iskanja še ni.", + "Search shortcuts saved.": "Bližnjice iskanja so shranjene.", + "Add shortcut": "Dodaj bližnjico", + "Save search shortcuts": "Shrani bližnjice iskanja", + "The search shortcuts could not be loaded.": "Bližnjic iskanja ni bilo mogoče naložiti.", + "The search shortcuts could not be saved.": "Bližnjic iskanja ni bilo mogoče shraniti.", + "Search {name} for {query}": "Išči v {name}: {query}", + "Type what to search for in {name}": "Vnesite, kaj iskati v {name}" } } diff --git a/l10n/sq.js b/l10n/sq.js index 0e6961f3d..0bd833a22 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Sipas alfabetit", "Most used" : "Më të përdorurat", "Last used" : "Përdorur së fundi", - "At random" : "Rastësisht" + "At random" : "Rastësisht", + "Search shortcuts" : "Shkurtore kërkimi", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Një kërkim që nis me një parashtesë, si !t printer, shkon drejt e në atë faqe në një skedë të re. Duke shkruar ? në kutinë e kërkimit shfaqen shkurtoret.", + "Prefix" : "Parashtesë", + "Address with {query}" : "Adresë me {query}", + "No search shortcuts yet." : "Ende nuk ka shkurtore kërkimi.", + "Search shortcuts saved." : "Shkurtoret e kërkimit u ruajtën.", + "Add shortcut" : "Shto shkurtore", + "Save search shortcuts" : "Ruaj shkurtoret e kërkimit", + "The search shortcuts could not be loaded." : "Shkurtoret e kërkimit nuk u ngarkuan.", + "The search shortcuts could not be saved." : "Shkurtoret e kërkimit nuk u ruajtën.", + "Search {name} for {query}" : "Kërko në {name} për {query}", + "Type what to search for in {name}" : "Shkruaj çfarë të kërkosh në {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sq.json b/l10n/sq.json index 558053019..5ac4b139f 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Sipas alfabetit", "Most used": "Më të përdorurat", "Last used": "Përdorur së fundi", - "At random": "Rastësisht" + "At random": "Rastësisht", + "Search shortcuts": "Shkurtore kërkimi", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Një kërkim që nis me një parashtesë, si !t printer, shkon drejt e në atë faqe në një skedë të re. Duke shkruar ? në kutinë e kërkimit shfaqen shkurtoret.", + "Prefix": "Parashtesë", + "Address with {query}": "Adresë me {query}", + "No search shortcuts yet.": "Ende nuk ka shkurtore kërkimi.", + "Search shortcuts saved.": "Shkurtoret e kërkimit u ruajtën.", + "Add shortcut": "Shto shkurtore", + "Save search shortcuts": "Ruaj shkurtoret e kërkimit", + "The search shortcuts could not be loaded.": "Shkurtoret e kërkimit nuk u ngarkuan.", + "The search shortcuts could not be saved.": "Shkurtoret e kërkimit nuk u ruajtën.", + "Search {name} for {query}": "Kërko në {name} për {query}", + "Type what to search for in {name}": "Shkruaj çfarë të kërkosh në {name}" } } diff --git a/l10n/sr.js b/l10n/sr.js index d8bca7a02..7a730dee2 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -1323,6 +1323,18 @@ OC.L10N.register( "Alphabetically" : "Абецедно", "Most used" : "Најкоришћеније", "Last used" : "Последње коришћено", - "At random" : "Насумично" + "At random" : "Насумично", + "Search shortcuts" : "Пречице за претрагу", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Претрага која почиње префиксом, нпр. !t printer, иде директно на ту страницу у новој картици. Уносом ? у поље за претрагу приказују се пречице.", + "Prefix" : "Префикс", + "Address with {query}" : "Адреса са {query}", + "No search shortcuts yet." : "Још нема пречица за претрагу.", + "Search shortcuts saved." : "Пречице за претрагу су сачуване.", + "Add shortcut" : "Додај пречицу", + "Save search shortcuts" : "Сачувај пречице за претрагу", + "The search shortcuts could not be loaded." : "Пречице за претрагу нису могле да се учитају.", + "The search shortcuts could not be saved." : "Пречице за претрагу нису могле да се сачувају.", + "Search {name} for {query}" : "Претражи {name} за {query}", + "Type what to search for in {name}" : "Унесите шта тражите у {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/sr.json b/l10n/sr.json index 9fc9ef3b4..b2c919ccb 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -1300,6 +1300,18 @@ "Alphabetically": "Абецедно", "Most used": "Најкоришћеније", "Last used": "Последње коришћено", - "At random": "Насумично" + "At random": "Насумично", + "Search shortcuts": "Пречице за претрагу", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Претрага која почиње префиксом, нпр. !t printer, иде директно на ту страницу у новој картици. Уносом ? у поље за претрагу приказују се пречице.", + "Prefix": "Префикс", + "Address with {query}": "Адреса са {query}", + "No search shortcuts yet.": "Још нема пречица за претрагу.", + "Search shortcuts saved.": "Пречице за претрагу су сачуване.", + "Add shortcut": "Додај пречицу", + "Save search shortcuts": "Сачувај пречице за претрагу", + "The search shortcuts could not be loaded.": "Пречице за претрагу нису могле да се учитају.", + "The search shortcuts could not be saved.": "Пречице за претрагу нису могле да се сачувају.", + "Search {name} for {query}": "Претражи {name} за {query}", + "Type what to search for in {name}": "Унесите шта тражите у {name}" } } diff --git a/l10n/sv.js b/l10n/sv.js index 9f2c290bf..c6779b6f7 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "I bokstavsordning", "Most used" : "Mest använda", "Last used" : "Senast använda", - "At random" : "Slumpmässigt" + "At random" : "Slumpmässigt", + "Search shortcuts" : "Sökgenvägar", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "En sökning som börjar med ett prefix, till exempel !t printer, går direkt till den webbplatsen i en ny flik. Skriv ? i en sökruta för att se genvägarna.", + "Prefix" : "Prefix", + "Address with {query}" : "Adress med {query}", + "No search shortcuts yet." : "Inga sökgenvägar ännu.", + "Search shortcuts saved." : "Sökgenvägarna har sparats.", + "Add shortcut" : "Lägg till genväg", + "Save search shortcuts" : "Spara sökgenvägar", + "The search shortcuts could not be loaded." : "Sökgenvägarna kunde inte läsas in.", + "The search shortcuts could not be saved." : "Sökgenvägarna kunde inte sparas.", + "Search {name} for {query}" : "Sök i {name} efter {query}", + "Type what to search for in {name}" : "Skriv vad du vill söka efter i {name}" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sv.json b/l10n/sv.json index 3a86552d0..84408ac33 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -1299,6 +1299,18 @@ "Alphabetically": "I bokstavsordning", "Most used": "Mest använda", "Last used": "Senast använda", - "At random": "Slumpmässigt" + "At random": "Slumpmässigt", + "Search shortcuts": "Sökgenvägar", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "En sökning som börjar med ett prefix, till exempel !t printer, går direkt till den webbplatsen i en ny flik. Skriv ? i en sökruta för att se genvägarna.", + "Prefix": "Prefix", + "Address with {query}": "Adress med {query}", + "No search shortcuts yet.": "Inga sökgenvägar ännu.", + "Search shortcuts saved.": "Sökgenvägarna har sparats.", + "Add shortcut": "Lägg till genväg", + "Save search shortcuts": "Spara sökgenvägar", + "The search shortcuts could not be loaded.": "Sökgenvägarna kunde inte läsas in.", + "The search shortcuts could not be saved.": "Sökgenvägarna kunde inte sparas.", + "Search {name} for {query}": "Sök i {name} efter {query}", + "Type what to search for in {name}": "Skriv vad du vill söka efter i {name}" } } diff --git a/l10n/tr.js b/l10n/tr.js index 3089b26ee..a6c2bc1c4 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "Alfabetik", "Most used" : "En çok kullanılan", "Last used" : "Son kullanılan", - "At random" : "Rastgele" + "At random" : "Rastgele", + "Search shortcuts" : "Arama kısayolları", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "!t printer gibi bir önekle başlayan arama, doğrudan o siteye yeni bir sekmede gider. Arama kutusuna ? yazınca kısayollar listelenir.", + "Prefix" : "Önek", + "Address with {query}" : "{query} içeren adres", + "No search shortcuts yet." : "Henüz arama kısayolu yok.", + "Search shortcuts saved." : "Arama kısayolları kaydedildi.", + "Add shortcut" : "Kısayol ekle", + "Save search shortcuts" : "Arama kısayollarını kaydet", + "The search shortcuts could not be loaded." : "Arama kısayolları yüklenemedi.", + "The search shortcuts could not be saved." : "Arama kısayolları kaydedilemedi.", + "Search {name} for {query}" : "{name} içinde {query} ara", + "Type what to search for in {name}" : "{name} içinde ne aranacağını yaz" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/tr.json b/l10n/tr.json index 155e964f0..b402ba896 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -1299,6 +1299,18 @@ "Alphabetically": "Alfabetik", "Most used": "En çok kullanılan", "Last used": "Son kullanılan", - "At random": "Rastgele" + "At random": "Rastgele", + "Search shortcuts": "Arama kısayolları", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "!t printer gibi bir önekle başlayan arama, doğrudan o siteye yeni bir sekmede gider. Arama kutusuna ? yazınca kısayollar listelenir.", + "Prefix": "Önek", + "Address with {query}": "{query} içeren adres", + "No search shortcuts yet.": "Henüz arama kısayolu yok.", + "Search shortcuts saved.": "Arama kısayolları kaydedildi.", + "Add shortcut": "Kısayol ekle", + "Save search shortcuts": "Arama kısayollarını kaydet", + "The search shortcuts could not be loaded.": "Arama kısayolları yüklenemedi.", + "The search shortcuts could not be saved.": "Arama kısayolları kaydedilemedi.", + "Search {name} for {query}": "{name} içinde {query} ara", + "Type what to search for in {name}": "{name} içinde ne aranacağını yaz" } } diff --git a/l10n/uk.js b/l10n/uk.js index be5b75f80..ffc923a4a 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -1332,7 +1332,19 @@ OC.L10N.register( "Alphabetically" : "За абеткою", "Most used" : "Найчастіше використовувані", "Last used" : "Останні використані", - "At random" : "Випадково" + "At random" : "Випадково", + "Search shortcuts" : "Ярлики пошуку", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts." : "Пошук, що починається з префікса, наприклад !t printer, одразу відкриває цей сайт у новій вкладці. Введіть ? у полі пошуку, щоб побачити ярлики.", + "Prefix" : "Префікс", + "Address with {query}" : "Адреса з {query}", + "No search shortcuts yet." : "Ярликів пошуку ще немає.", + "Search shortcuts saved." : "Ярлики пошуку збережено.", + "Add shortcut" : "Додати ярлик", + "Save search shortcuts" : "Зберегти ярлики пошуку", + "The search shortcuts could not be loaded." : "Не вдалося завантажити ярлики пошуку.", + "The search shortcuts could not be saved." : "Не вдалося зберегти ярлики пошуку.", + "Search {name} for {query}" : "Шукати в {name}: {query}", + "Type what to search for in {name}" : "Введіть, що шукати в {name}" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/uk.json b/l10n/uk.json index 5696c7113..ad873f69a 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -1300,6 +1300,18 @@ "Alphabetically": "За абеткою", "Most used": "Найчастіше використовувані", "Last used": "Останні використані", - "At random": "Випадково" + "At random": "Випадково", + "Search shortcuts": "Ярлики пошуку", + "A search that starts with a prefix, such as !t printer, goes straight to that site in a new tab. Typing ? in a search box lists the shortcuts.": "Пошук, що починається з префікса, наприклад !t printer, одразу відкриває цей сайт у новій вкладці. Введіть ? у полі пошуку, щоб побачити ярлики.", + "Prefix": "Префікс", + "Address with {query}": "Адреса з {query}", + "No search shortcuts yet.": "Ярликів пошуку ще немає.", + "Search shortcuts saved.": "Ярлики пошуку збережено.", + "Add shortcut": "Додати ярлик", + "Save search shortcuts": "Зберегти ярлики пошуку", + "The search shortcuts could not be loaded.": "Не вдалося завантажити ярлики пошуку.", + "The search shortcuts could not be saved.": "Не вдалося зберегти ярлики пошуку.", + "Search {name} for {query}": "Шукати в {name}: {query}", + "Type what to search for in {name}": "Введіть, що шукати в {name}" } } diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index f4816feb7..6497dffac 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -94,7 +94,7 @@ class PageController extends Controller { * REQ-QSEARCH-004). * @param SearchShortcutService $searchShortcuts The search shortcuts every * search box honours - * (REQ-QSP-002). + * (REQ-SPX-002). */ public function __construct( IRequest $request, diff --git a/lib/Controller/SearchShortcutsController.php b/lib/Controller/SearchShortcutsController.php index 376cd2d04..c6b7b2204 100644 --- a/lib/Controller/SearchShortcutsController.php +++ b/lib/Controller/SearchShortcutsController.php @@ -4,7 +4,7 @@ * SearchShortcutsController * * Administrator endpoints for the search shortcuts (search-ai-prefix-shortcuts, - * REQ-QSP-001). Every user receives the list through the workspace initial + * REQ-SPX-001). Every user receives the list through the workspace initial * state; only administrators read and write it here. * * @category Controller diff --git a/lib/Service/InitialStateBuilder.php b/lib/Service/InitialStateBuilder.php index 1ebf80957..0c3903dd3 100644 --- a/lib/Service/InitialStateBuilder.php +++ b/lib/Service/InitialStateBuilder.php @@ -340,7 +340,7 @@ public function setQuicksearchFallbackTarget(string $fallbackTarget): self { /** * Set the search shortcuts every search box honours (search-ai-prefix-shortcuts, - * REQ-QSP-002). Optional key, like `quicksearchFallbackTarget`: the JS reader + * REQ-SPX-002). Optional key, like `quicksearchFallbackTarget`: the JS reader * defaults it to an empty list. * * @param array $shortcuts Validated `{prefix, name, urlTemplate}` entries. diff --git a/lib/Service/SearchShortcutService.php b/lib/Service/SearchShortcutService.php index 710610424..b31f43fab 100644 --- a/lib/Service/SearchShortcutService.php +++ b/lib/Service/SearchShortcutService.php @@ -4,7 +4,7 @@ * SearchShortcutService * * The administrator's search shortcuts (search-ai-prefix-shortcuts, - * REQ-QSP-001): a prefix such as `!t`, a name and an https address with + * REQ-SPX-001): a prefix such as `!t`, a name and an https address with * `{query}`. A query in a LaunchPad search box that starts with a known * prefix goes straight to that site; the query never passes through the * LaunchPad server. @@ -138,7 +138,7 @@ private function validate(array $raw): array { $template = trim(string: (string)($entry['urlTemplate'] ?? '')); if ($this->adminSettings->isValidQuicksearchFallbackUrlTemplate(value: $template) === false) { - throw new InvalidArgumentException(message: 'The address must be an https address containing {query}: ' . $prefix); + throw new InvalidArgumentException(message: 'Use an https address that contains {query}: ' . $prefix); } $seen[$prefix] = true; diff --git a/src/components/RuntimeShellSearch.vue b/src/components/RuntimeShellSearch.vue index 58b81237e..05c605023 100644 --- a/src/components/RuntimeShellSearch.vue +++ b/src/components/RuntimeShellSearch.vue @@ -37,7 +37,7 @@ :id="listboxId" class="runtime-shell-search__results" role="listbox" - :aria-label="t('launchpad', 'Matching tiles')"> + :aria-label="listboxLabel"> @@ -179,6 +179,14 @@ export default { type: String, default: '', }, + + /** + * The admin's search shortcuts `{prefix, name, urlTemplate}` (REQ-SPX-002). + */ + shortcuts: { + type: Array, + default: () => [], + }, }, emits: ['open', 'filter', 'fallback', 'clear'], @@ -197,6 +205,7 @@ export default { this.$emit('fallback', action) }, getFallbackTarget: () => this.fallbackTarget, + getShortcuts: () => this.shortcuts, }), instanceId: `runtime-shell-search-${(instanceCounter += 1)}`, @@ -239,6 +248,22 @@ export default { * @spec openspec/specs/tile-quick-search/spec.md#req-qsearch-003 * @return {string|null} */ + /** + * Whether the results are shortcuts rather than tiles (REQ-SPX-002/003). + * + * @spec openspec/specs/tile-quick-search/spec.md + */ + showsShortcuts() { + return Boolean(this.search.state.results[0]?.kind) + }, + + /** @spec openspec/specs/tile-quick-search/spec.md */ + listboxLabel() { + return this.showsShortcuts + ? this.t('launchpad', 'Search shortcuts') + : this.t('launchpad', 'Matching tiles') + }, + ariaControls() { return this.hasResults ? this.listboxId : null }, @@ -290,6 +315,11 @@ export default { if (count === 0) { return this.noResultsMessage } + if (this.showsShortcuts) { + return this.search.state.results + .map((result) => this.resultLabel(result)) + .join(', ') + } return this.n( 'launchpad', '%n matching tile', @@ -329,6 +359,10 @@ export default { }, watch: { + shortcuts() { + this.search.setItems(this.items) + }, + items: { immediate: true, /** @@ -376,6 +410,28 @@ export default { * @param {number} index the option's index in `results`. * @return {string} */ + /** + * The visible text of a result: a tile's label, a listed shortcut, + * or where a shortcut will search (REQ-SPX-002/003). + * + * @param {object} result A result from the composable. + * @return {string} The text. + * @spec openspec/specs/tile-quick-search/spec.md + */ + resultLabel(result) { + if (result.kind === 'shortcut') { + return result.rest + ? this.t('launchpad', 'Search {name} for {query}', { + name: result.shortcut.name, + query: result.rest, + }) + : this.t('launchpad', 'Type what to search for in {name}', { + name: result.shortcut.name, + }) + } + return result.label + }, + optionId(index) { return `${this.listboxId}-option-${index}` }, @@ -443,7 +499,8 @@ export default { */ emitFilter() { const query = this.search.state.query - if (!query) { + // A shortcut query is not a tile filter: no tile is dimmed. + if (!query || this.showsShortcuts) { this.$emit('filter', null) return } diff --git a/src/components/Widgets/Renderers/SearchWidget.vue b/src/components/Widgets/Renderers/SearchWidget.vue index b48e54256..b0156959f 100644 --- a/src/components/Widgets/Renderers/SearchWidget.vue +++ b/src/components/Widgets/Renderers/SearchWidget.vue @@ -9,6 +9,7 @@ :items="searchableTiles" :placeholder="content.placeholder || ''" :fallbackTarget="effectiveFallbackTarget" + :shortcuts="injectedSearchShortcuts" @open="onSearchOpen" @filter="onSearchFilter" @fallback="onSearchFallback" @@ -64,6 +65,15 @@ export default { from: 'quicksearchFallbackTarget', default: 'none', }, + + /** + * The admin's search shortcuts (REQ-SPX-002). An empty list keeps + * the box as it was. + */ + injectedSearchShortcuts: { + from: 'searchShortcuts', + default: () => [], + }, }, props: { diff --git a/src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js b/src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js new file mode 100644 index 000000000..761c46021 --- /dev/null +++ b/src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js @@ -0,0 +1,138 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for search shortcuts in `RuntimeShellSearch.vue` and + * `useTileSearch.js` (search-ai-prefix-shortcuts, REQ-SPX-002, REQ-SPX-003): + * a known prefix shows one result naming the site and opens it on Enter, + * an unknown prefix falls through to tile filtering, and `?` or `!` lists + * the shortcuts. + */ + +import { mount } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import RuntimeShellSearch from '../RuntimeShellSearch.vue' +import { parsePrefix, shortcutUrl } from '../../composables/useTileSearch.js' + +const SHORTCUTS = [ + { + prefix: '!t', + name: 'TOPdesk', + urlTemplate: 'https://topdesk.gemeente.nl/tas/secure/search?q={query}', + }, + { + prefix: '!z', + name: 'Zaaksysteem', + urlTemplate: 'https://zaken.gemeente.nl/zoek?q={query}', + }, +] +const ITEMS = [ + { id: 'r1', label: '!x rapport' }, + { id: 'a1', label: 'Agenda' }, +] + +describe('search shortcuts', () => { + let wrapper + + beforeEach(() => { + globalThis.t = (_app, key) => key + }) + + afterEach(() => { + wrapper?.unmount() + }) + + function mountSearch() { + wrapper = mount(RuntimeShellSearch, { + attachTo: document.body, + props: { items: ITEMS, fallbackTarget: 'none', shortcuts: SHORTCUTS }, + }) + return wrapper + } + + async function type(value) { + const input = wrapper.find('input') + input.element.value = value + await input.trigger('input') + } + + it('parses the first word as a prefix, case-insensitively, and encodes the rest', () => { + expect(parsePrefix('!T printer 3e verdieping', SHORTCUTS)).toEqual({ + shortcut: SHORTCUTS[0], + rest: 'printer 3e verdieping', + }) + expect(parsePrefix('!x rapport', SHORTCUTS)).toBeNull() + expect(parsePrefix('printer !t', SHORTCUTS)).toBeNull() + expect(shortcutUrl(SHORTCUTS[0], 'printer 3e verdieping')).toBe( + 'https://topdesk.gemeente.nl/tas/secure/search?q=printer%203e%20verdieping', + ) + expect( + shortcutUrl({ urlTemplate: 'javascript:alert({query})' }, 'x'), + ).toBeNull() + }) + + it('REQ-SPX-002: the only result names the site and Enter opens it, without dimming tiles', async () => { + mountSearch() + await type('!t printer 3e verdieping') + + const options = wrapper.findAll('[data-test="quick-search-option"]') + expect(options).toHaveLength(1) + expect(options[0].text()).toBe('Search TOPdesk for printer 3e verdieping') + expect(wrapper.emitted('filter').at(-1)).toEqual([null]) + + await wrapper.find('input').trigger('keydown', { key: 'Enter' }) + expect(wrapper.emitted('fallback').at(-1)).toEqual([ + { + type: 'web-search', + url: 'https://topdesk.gemeente.nl/tas/secure/search?q=printer%203e%20verdieping', + }, + ]) + expect(wrapper.emitted('open')).toBeUndefined() + }) + + it('REQ-SPX-002: a prefix with nothing after it opens nothing', async () => { + mountSearch() + await type('!t') + await wrapper.find('input').trigger('keydown', { key: 'Enter' }) + expect(wrapper.emitted('fallback')).toBeUndefined() + }) + + it('REQ-SPX-002: an unknown prefix filters tiles as before', async () => { + mountSearch() + await type('!x rapport') + + const options = wrapper.findAll('[data-test="quick-search-option"]') + expect(options.map((option) => option.text())).toEqual(['!x rapport']) + expect(wrapper.emitted('filter').at(-1)).toEqual([['r1']]) + }) + + it('REQ-SPX-003: ? lists the shortcuts in a labelled list, and Enter types the prefix', async () => { + mountSearch() + await type('?') + + const list = wrapper.find('[role="listbox"]') + expect(list.attributes('aria-label')).toBe('Search shortcuts') + expect( + wrapper + .findAll('[data-test="quick-search-option"]') + .map((option) => option.text()), + ).toEqual(['!t TOPdesk', '!z Zaaksysteem']) + expect(wrapper.find('[data-test="quick-search-status"]').text()).toBe( + '!t TOPdesk, !z Zaaksysteem', + ) + + await wrapper.find('input').trigger('keydown', { key: 'Enter' }) + expect(wrapper.vm.search.state.query).toBe('!t ') + expect(wrapper.emitted('fallback')).toBeUndefined() + }) + + it('REQ-SPX-003: ! alone lists them too; without shortcuts ? is an ordinary query', async () => { + mountSearch() + await type('!') + expect(wrapper.findAll('[data-test="quick-search-option"]')).toHaveLength(2) + + await wrapper.setProps({ shortcuts: [] }) + await type('?') + expect(wrapper.find('[role="listbox"]').exists()).toBe(false) + }) +}) diff --git a/src/components/admin/AdminSettings.vue b/src/components/admin/AdminSettings.vue index ce51f5d5b..794becd0a 100644 --- a/src/components/admin/AdminSettings.vue +++ b/src/components/admin/AdminSettings.vue @@ -253,6 +253,9 @@ + @@ -297,6 +300,7 @@ import OperationsTab from './tabs/OperationsTab.vue' import OrgNavigationTab from './tabs/OrgNavigationTab.vue' import ProfileFieldsTab from './tabs/ProfileFieldsTab.vue' import RolesPermissionsTab from './tabs/RolesPermissionsTab.vue' +import SearchShortcutsTab from './tabs/SearchShortcutsTab.vue' import SharingTab from './tabs/SharingTab.vue' import TemplatesPage from './tabs/TemplatesPage.vue' import VersioningAuditTab from './tabs/VersioningAuditTab.vue' @@ -322,6 +326,7 @@ export default { MetadataFieldsSettings, KioskTab, ProfileFieldsTab, + SearchShortcutsTab, SharingTab, OrgNavigationTab, DemoDataTab, @@ -419,6 +424,10 @@ export default { slug: 'profile-fields', label: this.t('launchpad', 'Profile fields'), }, + { + slug: 'search-shortcuts', + label: this.t('launchpad', 'Search shortcuts'), + }, ] }, }, diff --git a/src/components/admin/tabs/SearchShortcutsTab.vue b/src/components/admin/tabs/SearchShortcutsTab.vue new file mode 100644 index 000000000..7df55c5ea --- /dev/null +++ b/src/components/admin/tabs/SearchShortcutsTab.vue @@ -0,0 +1,217 @@ + + + + + + + diff --git a/src/components/admin/tabs/__tests__/SearchShortcutsTab.spec.js b/src/components/admin/tabs/__tests__/SearchShortcutsTab.spec.js new file mode 100644 index 000000000..1dda0399d --- /dev/null +++ b/src/components/admin/tabs/__tests__/SearchShortcutsTab.spec.js @@ -0,0 +1,105 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for `SearchShortcutsTab.vue` (REQ-SPX-001): the list + * loads, a shortcut is added and saved, and the server's refusal of a plain + * http address is shown. + */ + +import axios from '@nextcloud/axios' +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import SearchShortcutsTab from '../SearchShortcutsTab.vue' + +vi.mock('@nextcloud/axios', () => ({ default: { get: vi.fn(), put: vi.fn() } })) +vi.mock('@nextcloud/router', async (importOriginal) => ({ + ...(await importOriginal()), + generateUrl: (path) => path, +})) + +function settle() { + return new Promise((resolve) => setTimeout(resolve, 0)) +} +const stubs = { + NcButton: { + props: ['disabled'], + emits: ['click'], + template: + '', + }, +} + +describe('SearchShortcutsTab', () => { + beforeEach(() => { + axios.get.mockReset() + axios.put.mockReset() + }) + + it('adds the TOPdesk shortcut and saves the list', async () => { + axios.get.mockResolvedValue({ data: { shortcuts: [] } }) + axios.put.mockImplementation((url, body) => + Promise.resolve({ data: { shortcuts: body.shortcuts } }), + ) + const wrapper = mount(SearchShortcutsTab, { global: { stubs } }) + await settle() + await settle() + + await wrapper + .findAll('button') + .find((button) => button.text() === 'Add shortcut') + .trigger('click') + const inputs = wrapper.findAll('tbody input') + await inputs[0].setValue('!t') + await inputs[1].setValue('TOPdesk') + await inputs[2].setValue( + 'https://topdesk.gemeente.nl/tas/secure/search?q={query}', + ) + await wrapper.find('form').trigger('submit') + await settle() + + expect(axios.put).toHaveBeenCalledWith( + '/apps/launchpad/api/admin/search-shortcuts', + { + shortcuts: [ + { + prefix: '!t', + name: 'TOPdesk', + urlTemplate: + 'https://topdesk.gemeente.nl/tas/secure/search?q={query}', + }, + ], + }, + ) + expect(wrapper.text()).toContain('Search shortcuts saved.') + }) + + it('shows the server refusal of a plain http address', async () => { + axios.get.mockResolvedValue({ + data: { + shortcuts: [ + { + prefix: '!i', + name: 'Intranet', + urlTemplate: 'http://intranet.gemeente.nl/zoek?q={query}', + }, + ], + }, + }) + axios.put.mockRejectedValue({ + response: { + data: { error: 'Use an https address that contains {query}: !i' }, + }, + }) + const wrapper = mount(SearchShortcutsTab, { global: { stubs } }) + await settle() + await settle() + + await wrapper.find('form').trigger('submit') + await settle() + + expect(wrapper.find('[role="alert"]').text()).toBe( + 'Use an https address that contains {query}: !i', + ) + }) +}) diff --git a/src/composables/useTileSearch.js b/src/composables/useTileSearch.js index ede1885c8..aee01a3e6 100644 --- a/src/composables/useTileSearch.js +++ b/src/composables/useTileSearch.js @@ -291,6 +291,56 @@ export function isCtrlKFocusShortcut(event) { return Boolean(event.ctrlKey || event.metaKey) } +/** + * Split a query into a known search shortcut and the rest (REQ-SPX-002). + * The first word must equal a shortcut prefix, case-insensitively. + * + * @param {string} query The raw query. + * @param {Array<{prefix: string, name: string, urlTemplate: string}>} shortcuts The admin's shortcuts. + * @return {{shortcut: object, rest: string}|null} The match, or null. + * @spec openspec/specs/tile-quick-search/spec.md + */ +export function parsePrefix(query, shortcuts) { + const trimmed = String(query ?? '').trim() + if (!trimmed.startsWith('!') || !Array.isArray(shortcuts)) { + return null + } + const [first, ...rest] = trimmed.split(/\s+/) + const shortcut = shortcuts.find( + (entry) => String(entry?.prefix || '').toLowerCase() === first.toLowerCase(), + ) + return shortcut ? { shortcut, rest: rest.join(' ') } : null +} + +/** + * The address a shortcut opens for a query: its template with the + * URL-encoded query, only when the template is a valid https `{query}` + * address (REQ-SPX-002). + * + * @param {{urlTemplate: string}} shortcut The shortcut. + * @param {string} rest The query after the prefix. + * @return {string|null} The address, or null. + * @spec openspec/specs/tile-quick-search/spec.md + */ +export function shortcutUrl(shortcut, rest) { + if (!isValidFallbackTemplate(shortcut?.urlTemplate)) { + return null + } + return shortcut.urlTemplate.replace('{query}', encodeURIComponent(rest)) +} + +/** + * Whether a query asks for the shortcut list: `!` alone or `?` (REQ-SPX-003). + * + * @param {string} query The raw query. + * @return {boolean} True for the list. + * @spec openspec/specs/tile-quick-search/spec.md + */ +export function isShortcutListQuery(query) { + const trimmed = String(query ?? '').trim() + return trimmed === '!' || trimmed === '?' +} + /** * Factory — one instance per mounted search bar. Mirrors the * `useGridManager()` shape: a `reactive()` reactive `state` plus @@ -307,12 +357,24 @@ export function isCtrlKFocusShortcut(event) { * @param {() => string} [options.getFallbackTarget] returns the current * `quicksearch_fallback_target` value; consulted lazily so the caller * can change it without recreating the composable. + * @param {() => Array} [options.getShortcuts] returns the admin's + * search shortcuts (REQ-SPX-002); consulted on every recompute. * @return {object} the `{state, ...methods}` API — see inline method docs. * * @spec openspec/specs/tile-quick-search/spec.md */ export function useTileSearch(options = {}) { - const { onOpen, onFallback, getFallbackTarget } = options + const { onOpen, onFallback, getFallbackTarget, getShortcuts } = options + + /** + * The admin's shortcuts, or an empty list. + * + * @return {Array} Shortcuts. + */ + function shortcuts() { + const list = typeof getShortcuts === 'function' ? getShortcuts() : [] + return Array.isArray(list) ? list : [] + } const state = reactive({ query: '', @@ -337,6 +399,34 @@ export function useTileSearch(options = {}) { state.activeIndex = -1 return } + // REQ-SPX-003: `!` or `?` lists the shortcuts. + if (isShortcutListQuery(trimmed) && shortcuts().length > 0) { + state.results = shortcuts().map((shortcut) => ({ + id: `shortcut-list:${shortcut.prefix}`, + label: `${shortcut.prefix} ${shortcut.name}`, + kind: 'shortcut-list', + shortcut, + item: null, + })) + state.activeIndex = 0 + return + } + // REQ-SPX-002: a known prefix wins over tile ranking. + const prefixed = parsePrefix(trimmed, shortcuts()) + if (prefixed) { + state.results = [ + { + id: `shortcut:${prefixed.shortcut.prefix}`, + label: prefixed.shortcut.name, + kind: 'shortcut', + shortcut: prefixed.shortcut, + rest: prefixed.rest, + item: null, + }, + ] + state.activeIndex = 0 + return + } state.results = rankItems(trimmed, items) state.activeIndex = state.results.length > 0 ? 0 : -1 } @@ -412,6 +502,24 @@ export function useTileSearch(options = {}) { */ function pressEnter() { const result = activeResult() + // REQ-SPX-003: Enter on a listed shortcut types its prefix. + if (result && result.kind === 'shortcut-list') { + setQuery(`${result.shortcut.prefix} `) + return { type: 'shortcut-list', shortcut: result.shortcut } + } + // REQ-SPX-002: Enter on a shortcut opens its site in a new tab. + if (result && result.kind === 'shortcut') { + const url = result.rest + ? shortcutUrl(result.shortcut, result.rest) + : null + const action = url + ? { type: 'web-search', url } + : { type: FALLBACK_TARGET_NONE } + if (url && typeof onFallback === 'function') { + onFallback(action) + } + return action + } if (result) { if (typeof onOpen === 'function') { onOpen(result.item) diff --git a/src/utils/loadInitialState.js b/src/utils/loadInitialState.js index 66522a4aa..4c65b0b45 100644 --- a/src/utils/loadInitialState.js +++ b/src/utils/loadInitialState.js @@ -63,7 +63,7 @@ const PAGE_KEYS = { // PHP side yet simply omits it, and 'none' is the safe default // (no navigation on no-match Enter). quicksearchFallbackTarget: 'none', - // search-ai-prefix-shortcuts REQ-QSP-002: the admin's search + // search-ai-prefix-shortcuts REQ-SPX-002: the admin's search // shortcuts `{prefix, name, urlTemplate}`. Optional key; an empty // list keeps every search box as it was. searchShortcuts: [], diff --git a/tests/Unit/Service/InitialStateBuilderTest.php b/tests/Unit/Service/InitialStateBuilderTest.php index 5763c708e..4ab5f79a6 100644 --- a/tests/Unit/Service/InitialStateBuilderTest.php +++ b/tests/Unit/Service/InitialStateBuilderTest.php @@ -30,7 +30,7 @@ class InitialStateBuilderTest extends TestCase { /** - * REQ-QSP-002: the search shortcuts reach the workspace page. + * REQ-SPX-002: the search shortcuts reach the workspace page. * * @return void */ diff --git a/tests/Unit/Service/SearchShortcutServiceTest.php b/tests/Unit/Service/SearchShortcutServiceTest.php index d31c9d225..f3dae7220 100644 --- a/tests/Unit/Service/SearchShortcutServiceTest.php +++ b/tests/Unit/Service/SearchShortcutServiceTest.php @@ -3,7 +3,7 @@ /** * SearchShortcutServiceTest * - * Search shortcuts (search-ai-prefix-shortcuts, REQ-QSP-001): the prefix + * Search shortcuts (search-ai-prefix-shortcuts, REQ-SPX-001): the prefix * pattern, case-insensitive uniqueness, the https `{query}` template check * reused from the quick-search fallback, and the limit of 30. * From 51d4103ca765824e6dcf6374eaefdc0ac85bda54 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 08:55:24 +0200 Subject: [PATCH 06/21] docs(openspec): archive search-ai-prefix-shortcuts, d-search-prefix is built --- .../.openspec.yaml | 0 .../design.md | 7 +++ .../proposal.md | 0 .../specs/tile-quick-search/spec.md | 0 .../tasks.md | 9 ++++ .../search-ai-prefix-shortcuts/tasks.md | 9 ---- openspec/parity/capabilities.json | 7 +-- openspec/specs/tile-quick-search/spec.md | 49 +++++++++++++++++++ 8 files changed, 69 insertions(+), 12 deletions(-) rename openspec/changes/{search-ai-prefix-shortcuts => archive/2026-09-30-search-ai-prefix-shortcuts}/.openspec.yaml (100%) rename openspec/changes/{search-ai-prefix-shortcuts => archive/2026-09-30-search-ai-prefix-shortcuts}/design.md (79%) rename openspec/changes/{search-ai-prefix-shortcuts => archive/2026-09-30-search-ai-prefix-shortcuts}/proposal.md (100%) rename openspec/changes/{search-ai-prefix-shortcuts => archive/2026-09-30-search-ai-prefix-shortcuts}/specs/tile-quick-search/spec.md (100%) create mode 100644 openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/tasks.md delete mode 100644 openspec/changes/search-ai-prefix-shortcuts/tasks.md diff --git a/openspec/changes/search-ai-prefix-shortcuts/.openspec.yaml b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/.openspec.yaml similarity index 100% rename from openspec/changes/search-ai-prefix-shortcuts/.openspec.yaml rename to openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/.openspec.yaml diff --git a/openspec/changes/search-ai-prefix-shortcuts/design.md b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/design.md similarity index 79% rename from openspec/changes/search-ai-prefix-shortcuts/design.md rename to openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/design.md index fdc114c03..cba698c48 100644 --- a/openspec/changes/search-ai-prefix-shortcuts/design.md +++ b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/design.md @@ -36,3 +36,10 @@ Only administrators edit the list. Every user can use it. The query leaves the b - PHPUnit: prefix format, uniqueness, template check, the 30-entry limit. - Vitest: `parsePrefix` cases (known, unknown, prefix only, uppercase), result rendering, Enter opening the right address, `?` listing. - Playwright: with `!t` set to a test site, typing `!t printer` and Enter opens that site with `printer`. + +## What the build corrected (2026-09-30) + +- The Workspace quick search and the search widget are one component since the bar became a widget (`SearchWidget.vue` hosts `RuntimeShellSearch.vue`), so the shortcuts are wired once. +- The shortcut list has its own admin endpoints (`GET`/`PUT /api/admin/search-shortcuts`, `SearchShortcutService`) instead of another parameter on `AdminSettingsService::updateSettings()`; the https `{query}` check is the same method, made public. +- Prefixes are stored lower case. Enter on a prefix with nothing after it opens nothing, and a shortcut query dims no tiles. +- Enter on an entry of the `?` list types that prefix into the box. diff --git a/openspec/changes/search-ai-prefix-shortcuts/proposal.md b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/proposal.md similarity index 100% rename from openspec/changes/search-ai-prefix-shortcuts/proposal.md rename to openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/proposal.md diff --git a/openspec/changes/search-ai-prefix-shortcuts/specs/tile-quick-search/spec.md b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/specs/tile-quick-search/spec.md similarity index 100% rename from openspec/changes/search-ai-prefix-shortcuts/specs/tile-quick-search/spec.md rename to openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/specs/tile-quick-search/spec.md diff --git a/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/tasks.md b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/tasks.md new file mode 100644 index 000000000..c9ce6620c --- /dev/null +++ b/openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts/tasks.md @@ -0,0 +1,9 @@ +# Tasks: search-ai-prefix-shortcuts + +- [x] 1. Add `search_shortcuts` to `lib/Db/AdminSettingKey.php` and validate it in `AdminSettingsService` (prefix pattern, uniqueness, template check, limit 30). Verify: PHPUnit. +- [x] 2. Provide the list through `lib/Service/InitialStateBuilder.php`. Verify: initial-state contract test. +- [x] 3. Add `parsePrefix()` to `src/composables/useTileSearch.js` and route a match ahead of ranking. Verify: Vitest. +- [x] 4. Render the "Search for " result and the `?` list in `SearchWidget.vue` and `RuntimeShellSearch.vue`, opening through `useTileSearchHost.js`. Verify: Vitest and an axe check on the list. +- [x] 5. Add the admin section to edit shortcuts. Verify: Vitest. +- [ ] 6. Playwright: `!t printer` opens the configured site. Not written: the requirements carry `@e2e exclude` naming the PHPUnit and Vitest files that cover them. +- [x] 7. Add `nl` and `en` strings, fold the delta into `openspec/specs/tile-quick-search/spec.md` on archive, and set d-search-prefix to `built` in the parity matrix with evidence lines. diff --git a/openspec/changes/search-ai-prefix-shortcuts/tasks.md b/openspec/changes/search-ai-prefix-shortcuts/tasks.md deleted file mode 100644 index 89b996644..000000000 --- a/openspec/changes/search-ai-prefix-shortcuts/tasks.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tasks: search-ai-prefix-shortcuts - -- [ ] 1. Add `search_shortcuts` to `lib/Db/AdminSettingKey.php` and validate it in `AdminSettingsService` (prefix pattern, uniqueness, template check, limit 30). Verify: PHPUnit. -- [ ] 2. Provide the list through `lib/Service/InitialStateBuilder.php`. Verify: initial-state contract test. -- [ ] 3. Add `parsePrefix()` to `src/composables/useTileSearch.js` and route a match ahead of ranking. Verify: Vitest. -- [ ] 4. Render the "Search for " result and the `?` list in `SearchWidget.vue` and `RuntimeShellSearch.vue`, opening through `useTileSearchHost.js`. Verify: Vitest and an axe check on the list. -- [ ] 5. Add the admin section to edit shortcuts. Verify: Vitest. -- [ ] 6. Playwright: `!t printer` opens the configured site. -- [ ] 7. Add `nl` and `en` strings, fold the delta into `openspec/specs/tile-quick-search/spec.md` on archive, and set d-search-prefix to `built` in the parity matrix with evidence lines. diff --git a/openspec/parity/capabilities.json b/openspec/parity/capabilities.json index c2d5e72b6..09332f820 100644 --- a/openspec/parity/capabilities.json +++ b/openspec/parity/capabilities.json @@ -4984,11 +4984,12 @@ "homarr": "yes", "dashy": "yes", "built": { - "state": "specified", + "state": "built", + "evidence": "lib/Service/SearchShortcutService.php; src/composables/useTileSearch.js (parsePrefix); src/components/RuntimeShellSearch.vue; src/components/admin/tabs/SearchShortcutsTab.vue", "owner": "ConductionNL/launchpad", - "change": "openspec/changes/search-ai-prefix-shortcuts" + "change": "openspec/changes/archive/2026-09-30-search-ai-prefix-shortcuts" }, - "reachedOn": "nothing", + "reachedOn": "Any search widget: type !t printer (a prefix the administrator set under Admin settings > LaunchPad > Search shortcuts) or ? for the list", "evidence": { "workspace-365": "https://support.workspace365.net/en/articles/175653-global-search users 'search by category' (Announcements, Apps, Email, Files, Folders, Knowledge articles, People, plus OpenIMS or Egnyte when configured); no prefix syntax to send a query to another site (read 2026-09-26)", "microsoft-viva": "not checked: Microsoft 365 roadmap 562015 'Scoped Search for SharePoint app in Teams' scopes search to SharePoint global search (cached roadmap, read 2026-09-26); no prefix to an external site found", diff --git a/openspec/specs/tile-quick-search/spec.md b/openspec/specs/tile-quick-search/spec.md index b92fe85ae..2fa97ffc9 100644 --- a/openspec/specs/tile-quick-search/spec.md +++ b/openspec/specs/tile-quick-search/spec.md @@ -251,3 +251,52 @@ cannot see on screen, or cannot find tiles they can. - WHEN its title is resolved - THEN the widget type's display name MUST be used - AND the generic fallback word MUST be used only when nothing else resolves + +### Requirement: Administrators define search shortcuts (REQ-SPX-001) + +@e2e exclude Playwright is not wired for search shortcuts yet; covered by tests/Unit/Service/SearchShortcutServiceTest.php and src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js + +An administrator MUST be able to define up to 30 search shortcuts, each with a prefix (`!` and 1 to 10 letters or digits, unique regardless of case), a name, and an https address containing `{query}`. The server MUST refuse an entry that breaks any of these rules. + +#### Scenario: Add the TOPdesk shortcut + +- **GIVEN** Noor is an administrator on the LaunchPad admin page, section "Search shortcuts" +- **WHEN** she adds prefix `!t`, name "TOPdesk" and address `https://topdesk.gemeente.nl/tas/secure/search?q={query}` and saves +- **THEN** the shortcut is listed + +#### Scenario: Plain http is refused + +- **GIVEN** Noor adds a shortcut +- **WHEN** she enters the address `http://intranet.gemeente.nl/zoek?q={query}` +- **THEN** the save fails with "Use an https address that contains {query}" + +### Requirement: A prefix sends the query to its site (REQ-SPX-002) + +@e2e exclude Playwright is not wired for search shortcuts yet; covered by tests/Unit/Service/SearchShortcutServiceTest.php and src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js + +In the search widget and the Workspace quick search, a query whose first word is a known prefix MUST show a single result naming the site and the rest of the query, and Enter MUST open the site's address with the URL-encoded rest in a new tab. A query without a known prefix MUST behave as before: tile filtering, then the configured fallback. + +#### Scenario: Search TOPdesk + +- **GIVEN** the shortcut `!t` for "TOPdesk" +- **WHEN** Pieter types `!t printer 3e verdieping` in the quick search +- **THEN** the only result reads "Search TOPdesk for printer 3e verdieping" +- **AND WHEN** he presses Enter, a new tab opens `https://topdesk.gemeente.nl/tas/secure/search?q=printer%203e%20verdieping` + +#### Scenario: Unknown prefix falls through + +- **GIVEN** no shortcut `!x` +- **WHEN** Pieter types `!x rapport` +- **THEN** the box filters tiles for `!x rapport` and applies the normal fallback + +### Requirement: Users can see the shortcuts (REQ-SPX-003) + +@e2e exclude Playwright is not wired for search shortcuts yet; covered by tests/Unit/Service/SearchShortcutServiceTest.php and src/components/__tests__/RuntimeShellSearch.shortcuts.spec.js + +Typing `!` alone or `?` in a search box MUST list the available shortcuts with their prefix and name, announced to screen readers. + +#### Scenario: List the shortcuts + +- **GIVEN** shortcuts `!t` "TOPdesk" and `!z` "Zaaksysteem" +- **WHEN** Pieter types `?` in the search widget +- **THEN** he sees "!t TOPdesk" and "!z Zaaksysteem" From 3ffdb753d65ddddf165906c51fa2b92b75cedc0a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 09:14:23 +0200 Subject: [PATCH 07/21] feat(tiles): import a browser bookmarks file, each chosen folder becoming a group of tiles The browser reads the file (Firefox, Chrome and Edge exports), shows folders and loose bookmarks to pick from, and refuses files over 5 MB or 2,000 bookmarks. The server keeps only http and https addresses and lists the rest as skipped, checks the widget quota once for the whole import (409 names the room left), and creates a container of link tiles per folder plus loose tiles at the bottom in one transaction. The entry sits in the active dashboard's menu for people who may add widgets. REQ-BMI-001..004 (launcher-bookmark-import) --- appinfo/routes.php | 2 + l10n/be.js | 17 +- l10n/be.json | 21 +- l10n/bg.js | 17 +- l10n/bg.json | 20 +- l10n/bs.js | 17 +- l10n/bs.json | 21 +- l10n/ca.js | 17 +- l10n/ca.json | 20 +- l10n/cs.js | 17 +- l10n/cs.json | 21 +- l10n/da.js | 17 +- l10n/da.json | 20 +- l10n/de.js | 17 +- l10n/de.json | 20 +- l10n/el.js | 17 +- l10n/el.json | 20 +- l10n/en.js | 17 +- l10n/en.json | 20 +- l10n/es.js | 17 +- l10n/es.json | 20 +- l10n/et.js | 17 +- l10n/et.json | 20 +- l10n/fi.js | 17 +- l10n/fi.json | 20 +- l10n/fr.js | 17 +- l10n/fr.json | 20 +- l10n/ga.js | 17 +- l10n/ga.json | 23 +- l10n/hr.js | 17 +- l10n/hr.json | 21 +- l10n/hu.js | 17 +- l10n/hu.json | 20 +- l10n/is.js | 17 +- l10n/is.json | 20 +- l10n/it.js | 17 +- l10n/it.json | 20 +- l10n/lb.js | 17 +- l10n/lb.json | 20 +- l10n/lt.js | 17 +- l10n/lt.json | 21 +- l10n/lv.js | 17 +- l10n/lv.json | 21 +- l10n/mk.js | 17 +- l10n/mk.json | 20 +- l10n/mt.js | 17 +- l10n/mt.json | 22 +- l10n/nb.js | 17 +- l10n/nb.json | 20 +- l10n/nl.js | 17 +- l10n/nl.json | 20 +- l10n/pl.js | 17 +- l10n/pl.json | 21 +- l10n/pt.js | 17 +- l10n/pt.json | 20 +- l10n/rm.js | 17 +- l10n/rm.json | 20 +- l10n/ro.js | 17 +- l10n/ro.json | 21 +- l10n/ru.js | 17 +- l10n/ru.json | 21 +- l10n/sk.js | 17 +- l10n/sk.json | 21 +- l10n/sl.js | 17 +- l10n/sl.json | 22 +- l10n/sq.js | 17 +- l10n/sq.json | 20 +- l10n/sr.js | 17 +- l10n/sr.json | 21 +- l10n/sv.js | 17 +- l10n/sv.json | 20 +- l10n/tr.js | 17 +- l10n/tr.json | 20 +- l10n/uk.js | 17 +- l10n/uk.json | 21 +- lib/Controller/BookmarkImportController.php | 120 +++++++ lib/Service/BookmarkImportService.php | 340 ++++++++++++++++++ lib/Service/QuotaService.php | 34 ++ lib/actions.seed.json | 1 + .../Workspace/DashboardRowActions.vue | 23 ++ src/dialogs/BookmarkImportDialog.vue | 336 +++++++++++++++++ .../__tests__/BookmarkImportDialog.spec.js | 216 +++++++++++ src/utils/__tests__/parseBookmarks.spec.js | 156 ++++++++ src/utils/parseBookmarks.js | 138 +++++++ src/views/Views.vue | 26 ++ tests/Unit/Service/ActionAuthBaselineTest.php | 2 + .../Service/BookmarkImportServiceTest.php | 230 ++++++++++++ 87 files changed, 2938 insertions(+), 74 deletions(-) create mode 100644 lib/Controller/BookmarkImportController.php create mode 100644 lib/Service/BookmarkImportService.php create mode 100644 src/dialogs/BookmarkImportDialog.vue create mode 100644 src/dialogs/__tests__/BookmarkImportDialog.spec.js create mode 100644 src/utils/__tests__/parseBookmarks.spec.js create mode 100644 src/utils/parseBookmarks.js create mode 100644 tests/Unit/Service/BookmarkImportServiceTest.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 8def0c811..4c8f64225 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -307,6 +307,8 @@ 'requirements' => ['placementId' => '\d+']], ['name' => 'widgetApi#addWidget', 'url' => '/api/dashboard/{dashboardId}/widgets', 'verb' => 'POST'], ['name' => 'widgetApi#addTile', 'url' => '/api/dashboard/{dashboardId}/tile', 'verb' => 'POST'], + // Bookmark import (launcher-bookmark-import REQ-BMI-001..003). + ['name' => 'bookmarkImport#import', 'url' => '/api/dashboard/{dashboardId}/tiles/import', 'verb' => 'POST'], // REQ-CAL-003: calendar widget events endpoint. Registered BEFORE // the wildcard `/api/widgets/{placementId}` PUT/DELETE so the // literal `calendar` segment is matched first. diff --git a/l10n/be.js b/l10n/be.js index 76bf6f451..be95dc318 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -1335,6 +1335,21 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Не ўдалося загрузіць ярлыкі пошуку.", "The search shortcuts could not be saved." : "Не ўдалося захаваць ярлыкі пошуку.", "Search {name} for {query}" : "Шукаць у {name}: {query}", - "Type what to search for in {name}" : "Увядзіце, што шукаць у {name}" + "Type what to search for in {name}" : "Увядзіце, што шукаць у {name}", + "Import bookmarks" : "Імпартаваць закладкі", + "Import bookmarks…" : "Імпартаваць закладкі…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Выберыце файл закладак, які экспартуе ваш браўзер. Кожная выбраная папка стане групай плітак унізе гэтай панэлі.", + "Bookmarks file" : "Файл закладак", + "What to import" : "Што імпартаваць", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "У гэтым файле няма закладак.", + "Tiles added: {count}" : "Дададзена плітак: {count}", + "Import" : "Імпартаваць", + "This file is larger than 5 MB. Export one folder at a time." : "Гэты файл большы за 5 МБ. Экспартуйце па адной папцы.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "У гэтым файле больш за 2000 закладак. Экспартуйце па адной папцы.", + "This file could not be read as a bookmarks file." : "Гэты файл не ўдалося прачытаць як файл закладак.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Засталося месца на панэлі: {count}. Выберыце менш папак.", + "The import failed; nothing was added." : "Імпарт не ўдаўся; нічога не дададзена.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прапушчана {count} закладка: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладкі: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладак: толькі вэб-адрасы могуць стаць пліткамі"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/be.json b/l10n/be.json index 3d594f825..ea03a0987 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Не ўдалося загрузіць ярлыкі пошуку.", "The search shortcuts could not be saved.": "Не ўдалося захаваць ярлыкі пошуку.", "Search {name} for {query}": "Шукаць у {name}: {query}", - "Type what to search for in {name}": "Увядзіце, што шукаць у {name}" + "Type what to search for in {name}": "Увядзіце, што шукаць у {name}", + "Import bookmarks": "Імпартаваць закладкі", + "Import bookmarks…": "Імпартаваць закладкі…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Выберыце файл закладак, які экспартуе ваш браўзер. Кожная выбраная папка стане групай плітак унізе гэтай панэлі.", + "Bookmarks file": "Файл закладак", + "What to import": "Што імпартаваць", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "У гэтым файле няма закладак.", + "Tiles added: {count}": "Дададзена плітак: {count}", + "Import": "Імпартаваць", + "This file is larger than 5 MB. Export one folder at a time.": "Гэты файл большы за 5 МБ. Экспартуйце па адной папцы.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "У гэтым файле больш за 2000 закладак. Экспартуйце па адной папцы.", + "This file could not be read as a bookmarks file.": "Гэты файл не ўдалося прачытаць як файл закладак.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Засталося месца на панэлі: {count}. Выберыце менш папак.", + "The import failed; nothing was added.": "Імпарт не ўдаўся; нічога не дададзена.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Прапушчана {count} закладка: толькі вэб-адрасы могуць стаць пліткамі", + "Прапушчана {count} закладкі: толькі вэб-адрасы могуць стаць пліткамі", + "Прапушчана {count} закладак: толькі вэб-адрасы могуць стаць пліткамі" + ] } } diff --git a/l10n/bg.js b/l10n/bg.js index d25241b77..41a1b47d5 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Преките пътища за търсене не можаха да се заредят.", "The search shortcuts could not be saved." : "Преките пътища за търсене не можаха да се запазят.", "Search {name} for {query}" : "Търсене в {name} за {query}", - "Type what to search for in {name}" : "Въведете какво да търсите в {name}" + "Type what to search for in {name}" : "Въведете какво да търсите в {name}", + "Import bookmarks" : "Импортиране на отметки", + "Import bookmarks…" : "Импортиране на отметки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Изберете файла с отметки, който браузърът ви експортира. Всяка избрана папка става група плочки в долната част на това табло.", + "Bookmarks file" : "Файл с отметки", + "What to import" : "Какво да се импортира", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Този файл не съдържа отметки.", + "Tiles added: {count}" : "Добавени плочки: {count}", + "Import" : "Импортиране", + "This file is larger than 5 MB. Export one folder at a time." : "Този файл е по-голям от 5 MB. Експортирайте по една папка.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Този файл има повече от 2000 отметки. Експортирайте по една папка.", + "This file could not be read as a bookmarks file." : "Този файл не може да се прочете като файл с отметки.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Оставащо място на това табло: {count}. Изберете по-малко папки.", + "The import failed; nothing was added." : "Импортирането не успя; нищо не е добавено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} отметка е пропусната: само уеб адреси могат да станат плочки", "{count} отметки са пропуснати: само уеб адреси могат да станат плочки"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/bg.json b/l10n/bg.json index 56adfe841..019a22362 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Преките пътища за търсене не можаха да се заредят.", "The search shortcuts could not be saved.": "Преките пътища за търсене не можаха да се запазят.", "Search {name} for {query}": "Търсене в {name} за {query}", - "Type what to search for in {name}": "Въведете какво да търсите в {name}" + "Type what to search for in {name}": "Въведете какво да търсите в {name}", + "Import bookmarks": "Импортиране на отметки", + "Import bookmarks…": "Импортиране на отметки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Изберете файла с отметки, който браузърът ви експортира. Всяка избрана папка става група плочки в долната част на това табло.", + "Bookmarks file": "Файл с отметки", + "What to import": "Какво да се импортира", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Този файл не съдържа отметки.", + "Tiles added: {count}": "Добавени плочки: {count}", + "Import": "Импортиране", + "This file is larger than 5 MB. Export one folder at a time.": "Този файл е по-голям от 5 MB. Експортирайте по една папка.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Този файл има повече от 2000 отметки. Експортирайте по една папка.", + "This file could not be read as a bookmarks file.": "Този файл не може да се прочете като файл с отметки.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Оставащо място на това табло: {count}. Изберете по-малко папки.", + "The import failed; nothing was added.": "Импортирането не успя; нищо не е добавено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} отметка е пропусната: само уеб адреси могат да станат плочки", + "{count} отметки са пропуснати: само уеб адреси могат да станат плочки" + ] } } diff --git a/l10n/bs.js b/l10n/bs.js index 62caff64e..e13237edb 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Prečaci za pretragu nisu mogli biti učitani.", "The search shortcuts could not be saved." : "Prečaci za pretragu nisu mogli biti spremljeni.", "Search {name} for {query}" : "Pretraži {name} za {query}", - "Type what to search for in {name}" : "Upišite što tražite u {name}" + "Type what to search for in {name}" : "Upišite što tražite u {name}", + "Import bookmarks" : "Uvezi oznake", + "Import bookmarks…" : "Uvezi oznake…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Odaberite datoteku oznaka koju izvozi vaš preglednik. Svaka odabrana mapa postaje grupa pločica na dnu ove nadzorne ploče.", + "Bookmarks file" : "Datoteka oznaka", + "What to import" : "Šta uvesti", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ova datoteka nema oznaka.", + "Tiles added: {count}" : "Dodano pločica: {count}", + "Import" : "Uvezi", + "This file is larger than 5 MB. Export one folder at a time." : "Ova datoteka je veća od 5 MB. Izvezite jednu po jednu mapu.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ova datoteka ima više od 2.000 oznaka. Izvezite jednu po jednu mapu.", + "This file could not be read as a bookmarks file." : "Ova datoteka se ne može pročitati kao datoteka oznaka.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Preostalo mjesta na ovoj ploči: {count}. Odaberite manje mapa.", + "The import failed; nothing was added." : "Uvoz nije uspio; ništa nije dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} oznaka: samo web adrese mogu postati pločice", "Preskočene {count} oznake: samo web adrese mogu postati pločice", "Preskočeno {count} oznaka: samo web adrese mogu postati pločice"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/bs.json b/l10n/bs.json index 97aa7826f..fb30adff1 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Prečaci za pretragu nisu mogli biti učitani.", "The search shortcuts could not be saved.": "Prečaci za pretragu nisu mogli biti spremljeni.", "Search {name} for {query}": "Pretraži {name} za {query}", - "Type what to search for in {name}": "Upišite što tražite u {name}" + "Type what to search for in {name}": "Upišite što tražite u {name}", + "Import bookmarks": "Uvezi oznake", + "Import bookmarks…": "Uvezi oznake…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Odaberite datoteku oznaka koju izvozi vaš preglednik. Svaka odabrana mapa postaje grupa pločica na dnu ove nadzorne ploče.", + "Bookmarks file": "Datoteka oznaka", + "What to import": "Šta uvesti", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ova datoteka nema oznaka.", + "Tiles added: {count}": "Dodano pločica: {count}", + "Import": "Uvezi", + "This file is larger than 5 MB. Export one folder at a time.": "Ova datoteka je veća od 5 MB. Izvezite jednu po jednu mapu.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ova datoteka ima više od 2.000 oznaka. Izvezite jednu po jednu mapu.", + "This file could not be read as a bookmarks file.": "Ova datoteka se ne može pročitati kao datoteka oznaka.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Preostalo mjesta na ovoj ploči: {count}. Odaberite manje mapa.", + "The import failed; nothing was added.": "Uvoz nije uspio; ništa nije dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Preskočena {count} oznaka: samo web adrese mogu postati pločice", + "Preskočene {count} oznake: samo web adrese mogu postati pločice", + "Preskočeno {count} oznaka: samo web adrese mogu postati pločice" + ] } } diff --git a/l10n/ca.js b/l10n/ca.js index 378302230..8954da06b 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "No s'han pogut carregar les dreceres de cerca.", "The search shortcuts could not be saved." : "No s'han pogut desar les dreceres de cerca.", "Search {name} for {query}" : "Cerca {query} a {name}", - "Type what to search for in {name}" : "Escriviu què voleu cercar a {name}" + "Type what to search for in {name}" : "Escriviu què voleu cercar a {name}", + "Import bookmarks" : "Importa adreces d'interès", + "Import bookmarks…" : "Importa adreces d'interès…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Trieu el fitxer d'adreces d'interès que exporta el navegador. Cada carpeta que trieu es converteix en un grup de rajoles a la part inferior d'aquest tauler.", + "Bookmarks file" : "Fitxer d'adreces d'interès", + "What to import" : "Què cal importar", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Aquest fitxer no conté adreces d'interès.", + "Tiles added: {count}" : "Rajoles afegides: {count}", + "Import" : "Importa", + "This file is larger than 5 MB. Export one folder at a time." : "Aquest fitxer ocupa més de 5 MB. Exporteu les carpetes d'una en una.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Aquest fitxer té més de 2.000 adreces d'interès. Exporteu les carpetes d'una en una.", + "This file could not be read as a bookmarks file." : "Aquest fitxer no s'ha pogut llegir com a fitxer d'adreces d'interès.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Espai que queda en aquest tauler: {count}. Trieu menys carpetes.", + "The import failed; nothing was added." : "La importació ha fallat; no s'ha afegit res.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["S'ha omès {count} adreça d'interès: només les adreces web poden ser rajoles", "S'han omès {count} adreces d'interès: només les adreces web poden ser rajoles"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/ca.json b/l10n/ca.json index 7b3a43c40..f687b5978 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "No s'han pogut carregar les dreceres de cerca.", "The search shortcuts could not be saved.": "No s'han pogut desar les dreceres de cerca.", "Search {name} for {query}": "Cerca {query} a {name}", - "Type what to search for in {name}": "Escriviu què voleu cercar a {name}" + "Type what to search for in {name}": "Escriviu què voleu cercar a {name}", + "Import bookmarks": "Importa adreces d'interès", + "Import bookmarks…": "Importa adreces d'interès…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Trieu el fitxer d'adreces d'interès que exporta el navegador. Cada carpeta que trieu es converteix en un grup de rajoles a la part inferior d'aquest tauler.", + "Bookmarks file": "Fitxer d'adreces d'interès", + "What to import": "Què cal importar", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Aquest fitxer no conté adreces d'interès.", + "Tiles added: {count}": "Rajoles afegides: {count}", + "Import": "Importa", + "This file is larger than 5 MB. Export one folder at a time.": "Aquest fitxer ocupa més de 5 MB. Exporteu les carpetes d'una en una.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Aquest fitxer té més de 2.000 adreces d'interès. Exporteu les carpetes d'una en una.", + "This file could not be read as a bookmarks file.": "Aquest fitxer no s'ha pogut llegir com a fitxer d'adreces d'interès.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Espai que queda en aquest tauler: {count}. Trieu menys carpetes.", + "The import failed; nothing was added.": "La importació ha fallat; no s'ha afegit res.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "S'ha omès {count} adreça d'interès: només les adreces web poden ser rajoles", + "S'han omès {count} adreces d'interès: només les adreces web poden ser rajoles" + ] } } diff --git a/l10n/cs.js b/l10n/cs.js index a1e4d098a..9592240a6 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Zkratky vyhledávání se nepodařilo načíst.", "The search shortcuts could not be saved." : "Zkratky vyhledávání se nepodařilo uložit.", "Search {name} for {query}" : "Hledat {query} v {name}", - "Type what to search for in {name}" : "Napište, co hledat v {name}" + "Type what to search for in {name}" : "Napište, co hledat v {name}", + "Import bookmarks" : "Importovat záložky", + "Import bookmarks…" : "Importovat záložky…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Vyberte soubor záložek, který exportuje váš prohlížeč. Každá vybraná složka se stane skupinou dlaždic v dolní části této nástěnky.", + "Bookmarks file" : "Soubor záložek", + "What to import" : "Co importovat", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Tento soubor neobsahuje žádné záložky.", + "Tiles added: {count}" : "Přidané dlaždice: {count}", + "Import" : "Importovat", + "This file is larger than 5 MB. Export one folder at a time." : "Tento soubor je větší než 5 MB. Exportujte složky po jedné.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Tento soubor má více než 2 000 záložek. Exportujte složky po jedné.", + "This file could not be read as a bookmarks file." : "Tento soubor nelze přečíst jako soubor záložek.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Zbývající místo na této nástěnce: {count}. Vyberte méně složek.", + "The import failed; nothing was added." : "Import se nezdařil; nic nebylo přidáno.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Přeskočena {count} záložka: dlaždicí může být jen webová adresa", "Přeskočeny {count} záložky: dlaždicí může být jen webová adresa", "Přeskočeno {count} záložek: dlaždicí může být jen webová adresa"] }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/cs.json b/l10n/cs.json index a9f386104..d1360907f 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Zkratky vyhledávání se nepodařilo načíst.", "The search shortcuts could not be saved.": "Zkratky vyhledávání se nepodařilo uložit.", "Search {name} for {query}": "Hledat {query} v {name}", - "Type what to search for in {name}": "Napište, co hledat v {name}" + "Type what to search for in {name}": "Napište, co hledat v {name}", + "Import bookmarks": "Importovat záložky", + "Import bookmarks…": "Importovat záložky…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Vyberte soubor záložek, který exportuje váš prohlížeč. Každá vybraná složka se stane skupinou dlaždic v dolní části této nástěnky.", + "Bookmarks file": "Soubor záložek", + "What to import": "Co importovat", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Tento soubor neobsahuje žádné záložky.", + "Tiles added: {count}": "Přidané dlaždice: {count}", + "Import": "Importovat", + "This file is larger than 5 MB. Export one folder at a time.": "Tento soubor je větší než 5 MB. Exportujte složky po jedné.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Tento soubor má více než 2 000 záložek. Exportujte složky po jedné.", + "This file could not be read as a bookmarks file.": "Tento soubor nelze přečíst jako soubor záložek.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Zbývající místo na této nástěnce: {count}. Vyberte méně složek.", + "The import failed; nothing was added.": "Import se nezdařil; nic nebylo přidáno.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Přeskočena {count} záložka: dlaždicí může být jen webová adresa", + "Přeskočeny {count} záložky: dlaždicí může být jen webová adresa", + "Přeskočeno {count} záložek: dlaždicí může být jen webová adresa" + ] } } diff --git a/l10n/da.js b/l10n/da.js index eab9196d1..4ea308632 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Søgegenvejene kunne ikke indlæses.", "The search shortcuts could not be saved." : "Søgegenvejene kunne ikke gemmes.", "Search {name} for {query}" : "Søg i {name} efter {query}", - "Type what to search for in {name}" : "Skriv, hvad du vil søge efter i {name}" + "Type what to search for in {name}" : "Skriv, hvad du vil søge efter i {name}", + "Import bookmarks" : "Importér bogmærker", + "Import bookmarks…" : "Importér bogmærker…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Vælg den bogmærkefil, din browser eksporterer. Hver mappe, du vælger, bliver en gruppe felter nederst på dette dashboard.", + "Bookmarks file" : "Bogmærkefil", + "What to import" : "Hvad der skal importeres", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Denne fil indeholder ingen bogmærker.", + "Tiles added: {count}" : "Tilføjede felter: {count}", + "Import" : "Importér", + "This file is larger than 5 MB. Export one folder at a time." : "Filen er større end 5 MB. Eksportér én mappe ad gangen.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Filen har mere end 2.000 bogmærker. Eksportér én mappe ad gangen.", + "This file could not be read as a bookmarks file." : "Filen kunne ikke læses som en bogmærkefil.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Plads tilbage på dette dashboard: {count}. Vælg færre mapper.", + "The import failed; nothing was added." : "Importen mislykkedes; intet blev tilføjet.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bogmærke sprunget over: kun webadresser kan blive til felter", "{count} bogmærker sprunget over: kun webadresser kan blive til felter"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/da.json b/l10n/da.json index 0764f8f9e..fa9450a44 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Søgegenvejene kunne ikke indlæses.", "The search shortcuts could not be saved.": "Søgegenvejene kunne ikke gemmes.", "Search {name} for {query}": "Søg i {name} efter {query}", - "Type what to search for in {name}": "Skriv, hvad du vil søge efter i {name}" + "Type what to search for in {name}": "Skriv, hvad du vil søge efter i {name}", + "Import bookmarks": "Importér bogmærker", + "Import bookmarks…": "Importér bogmærker…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Vælg den bogmærkefil, din browser eksporterer. Hver mappe, du vælger, bliver en gruppe felter nederst på dette dashboard.", + "Bookmarks file": "Bogmærkefil", + "What to import": "Hvad der skal importeres", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Denne fil indeholder ingen bogmærker.", + "Tiles added: {count}": "Tilføjede felter: {count}", + "Import": "Importér", + "This file is larger than 5 MB. Export one folder at a time.": "Filen er større end 5 MB. Eksportér én mappe ad gangen.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Filen har mere end 2.000 bogmærker. Eksportér én mappe ad gangen.", + "This file could not be read as a bookmarks file.": "Filen kunne ikke læses som en bogmærkefil.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Plads tilbage på dette dashboard: {count}. Vælg færre mapper.", + "The import failed; nothing was added.": "Importen mislykkedes; intet blev tilføjet.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bogmærke sprunget over: kun webadresser kan blive til felter", + "{count} bogmærker sprunget over: kun webadresser kan blive til felter" + ] } } diff --git a/l10n/de.js b/l10n/de.js index bb53c9f5e..fb9f5b46f 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Die Suchkürzel konnten nicht geladen werden.", "The search shortcuts could not be saved." : "Die Suchkürzel konnten nicht gespeichert werden.", "Search {name} for {query}" : "In {name} nach {query} suchen", - "Type what to search for in {name}" : "Gib ein, wonach in {name} gesucht werden soll" + "Type what to search for in {name}" : "Gib ein, wonach in {name} gesucht werden soll", + "Import bookmarks" : "Lesezeichen importieren", + "Import bookmarks…" : "Lesezeichen importieren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Wähle die Lesezeichendatei, die dein Browser exportiert. Jeder gewählte Ordner wird zu einer Kachelgruppe unten auf diesem Dashboard.", + "Bookmarks file" : "Lesezeichendatei", + "What to import" : "Was importiert werden soll", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Diese Datei enthält keine Lesezeichen.", + "Tiles added: {count}" : "Hinzugefügte Kacheln: {count}", + "Import" : "Importieren", + "This file is larger than 5 MB. Export one folder at a time." : "Diese Datei ist größer als 5 MB. Exportiere jeweils einen Ordner.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Diese Datei hat mehr als 2.000 Lesezeichen. Exportiere jeweils einen Ordner.", + "This file could not be read as a bookmarks file." : "Diese Datei konnte nicht als Lesezeichendatei gelesen werden.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Platz auf diesem Dashboard: noch {count}. Wähle weniger Ordner.", + "The import failed; nothing was added." : "Der Import ist fehlgeschlagen; nichts wurde hinzugefügt.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden", "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/de.json b/l10n/de.json index a4b1b008e..41054c45e 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Die Suchkürzel konnten nicht geladen werden.", "The search shortcuts could not be saved.": "Die Suchkürzel konnten nicht gespeichert werden.", "Search {name} for {query}": "In {name} nach {query} suchen", - "Type what to search for in {name}": "Gib ein, wonach in {name} gesucht werden soll" + "Type what to search for in {name}": "Gib ein, wonach in {name} gesucht werden soll", + "Import bookmarks": "Lesezeichen importieren", + "Import bookmarks…": "Lesezeichen importieren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Wähle die Lesezeichendatei, die dein Browser exportiert. Jeder gewählte Ordner wird zu einer Kachelgruppe unten auf diesem Dashboard.", + "Bookmarks file": "Lesezeichendatei", + "What to import": "Was importiert werden soll", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Diese Datei enthält keine Lesezeichen.", + "Tiles added: {count}": "Hinzugefügte Kacheln: {count}", + "Import": "Importieren", + "This file is larger than 5 MB. Export one folder at a time.": "Diese Datei ist größer als 5 MB. Exportiere jeweils einen Ordner.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Diese Datei hat mehr als 2.000 Lesezeichen. Exportiere jeweils einen Ordner.", + "This file could not be read as a bookmarks file.": "Diese Datei konnte nicht als Lesezeichendatei gelesen werden.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Platz auf diesem Dashboard: noch {count}. Wähle weniger Ordner.", + "The import failed; nothing was added.": "Der Import ist fehlgeschlagen; nichts wurde hinzugefügt.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden", + "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden" + ] } } diff --git a/l10n/el.js b/l10n/el.js index b437cd029..0959b9196 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Δεν ήταν δυνατή η φόρτωση των συντομεύσεων αναζήτησης.", "The search shortcuts could not be saved." : "Δεν ήταν δυνατή η αποθήκευση των συντομεύσεων αναζήτησης.", "Search {name} for {query}" : "Αναζήτηση στο {name} για {query}", - "Type what to search for in {name}" : "Πληκτρολογήστε τι να αναζητηθεί στο {name}" + "Type what to search for in {name}" : "Πληκτρολογήστε τι να αναζητηθεί στο {name}", + "Import bookmarks" : "Εισαγωγή σελιδοδεικτών", + "Import bookmarks…" : "Εισαγωγή σελιδοδεικτών…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Επιλέξτε το αρχείο σελιδοδεικτών που εξάγει το πρόγραμμα περιήγησής σας. Κάθε φάκελος που επιλέγετε γίνεται ομάδα πλακιδίων στο κάτω μέρος αυτού του πίνακα.", + "Bookmarks file" : "Αρχείο σελιδοδεικτών", + "What to import" : "Τι θα εισαχθεί", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Αυτό το αρχείο δεν περιέχει σελιδοδείκτες.", + "Tiles added: {count}" : "Πλακίδια που προστέθηκαν: {count}", + "Import" : "Εισαγωγή", + "This file is larger than 5 MB. Export one folder at a time." : "Αυτό το αρχείο είναι μεγαλύτερο από 5 MB. Εξάγετε έναν φάκελο τη φορά.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Αυτό το αρχείο έχει περισσότερους από 2.000 σελιδοδείκτες. Εξάγετε έναν φάκελο τη φορά.", + "This file could not be read as a bookmarks file." : "Αυτό το αρχείο δεν ήταν δυνατό να διαβαστεί ως αρχείο σελιδοδεικτών.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Διαθέσιμος χώρος σε αυτόν τον πίνακα: {count}. Επιλέξτε λιγότερους φακέλους.", + "The import failed; nothing was added." : "Η εισαγωγή απέτυχε· δεν προστέθηκε τίποτα.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Παραλείφθηκε {count} σελιδοδείκτης: μόνο διευθύνσεις ιστού γίνονται πλακίδια", "Παραλείφθηκαν {count} σελιδοδείκτες: μόνο διευθύνσεις ιστού γίνονται πλακίδια"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/el.json b/l10n/el.json index 372eb89de..50297f4e3 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Δεν ήταν δυνατή η φόρτωση των συντομεύσεων αναζήτησης.", "The search shortcuts could not be saved.": "Δεν ήταν δυνατή η αποθήκευση των συντομεύσεων αναζήτησης.", "Search {name} for {query}": "Αναζήτηση στο {name} για {query}", - "Type what to search for in {name}": "Πληκτρολογήστε τι να αναζητηθεί στο {name}" + "Type what to search for in {name}": "Πληκτρολογήστε τι να αναζητηθεί στο {name}", + "Import bookmarks": "Εισαγωγή σελιδοδεικτών", + "Import bookmarks…": "Εισαγωγή σελιδοδεικτών…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Επιλέξτε το αρχείο σελιδοδεικτών που εξάγει το πρόγραμμα περιήγησής σας. Κάθε φάκελος που επιλέγετε γίνεται ομάδα πλακιδίων στο κάτω μέρος αυτού του πίνακα.", + "Bookmarks file": "Αρχείο σελιδοδεικτών", + "What to import": "Τι θα εισαχθεί", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Αυτό το αρχείο δεν περιέχει σελιδοδείκτες.", + "Tiles added: {count}": "Πλακίδια που προστέθηκαν: {count}", + "Import": "Εισαγωγή", + "This file is larger than 5 MB. Export one folder at a time.": "Αυτό το αρχείο είναι μεγαλύτερο από 5 MB. Εξάγετε έναν φάκελο τη φορά.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Αυτό το αρχείο έχει περισσότερους από 2.000 σελιδοδείκτες. Εξάγετε έναν φάκελο τη φορά.", + "This file could not be read as a bookmarks file.": "Αυτό το αρχείο δεν ήταν δυνατό να διαβαστεί ως αρχείο σελιδοδεικτών.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Διαθέσιμος χώρος σε αυτόν τον πίνακα: {count}. Επιλέξτε λιγότερους φακέλους.", + "The import failed; nothing was added.": "Η εισαγωγή απέτυχε· δεν προστέθηκε τίποτα.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Παραλείφθηκε {count} σελιδοδείκτης: μόνο διευθύνσεις ιστού γίνονται πλακίδια", + "Παραλείφθηκαν {count} σελιδοδείκτες: μόνο διευθύνσεις ιστού γίνονται πλακίδια" + ] } } diff --git a/l10n/en.js b/l10n/en.js index 031b339f7..6719f22cb 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -1324,7 +1324,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "The search shortcuts could not be loaded.", "The search shortcuts could not be saved." : "The search shortcuts could not be saved.", "Search {name} for {query}" : "Search {name} for {query}", - "Type what to search for in {name}" : "Type what to search for in {name}" + "Type what to search for in {name}" : "Type what to search for in {name}", + "Import bookmarks" : "Import bookmarks", + "Import bookmarks…" : "Import bookmarks…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.", + "Bookmarks file" : "Bookmarks file", + "What to import" : "What to import", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "This file holds no bookmarks.", + "Tiles added: {count}" : "Tiles added: {count}", + "Import" : "Import", + "This file is larger than 5 MB. Export one folder at a time." : "This file is larger than 5 MB. Export one folder at a time.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "This file has more than 2,000 bookmarks. Export one folder at a time.", + "This file could not be read as a bookmarks file." : "This file could not be read as a bookmarks file.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Room left on this dashboard: {count}. Pick fewer folders.", + "The import failed; nothing was added." : "The import failed; nothing was added.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark skipped: only web addresses can become tiles", "{count} bookmarks skipped: only web addresses can become tiles"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/en.json b/l10n/en.json index 74b6aa512..b0e9488ab 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -1289,6 +1289,24 @@ "The search shortcuts could not be loaded.": "The search shortcuts could not be loaded.", "The search shortcuts could not be saved.": "The search shortcuts could not be saved.", "Search {name} for {query}": "Search {name} for {query}", - "Type what to search for in {name}": "Type what to search for in {name}" + "Type what to search for in {name}": "Type what to search for in {name}", + "Import bookmarks": "Import bookmarks", + "Import bookmarks…": "Import bookmarks…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.", + "Bookmarks file": "Bookmarks file", + "What to import": "What to import", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "This file holds no bookmarks.", + "Tiles added: {count}": "Tiles added: {count}", + "Import": "Import", + "This file is larger than 5 MB. Export one folder at a time.": "This file is larger than 5 MB. Export one folder at a time.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "This file has more than 2,000 bookmarks. Export one folder at a time.", + "This file could not be read as a bookmarks file.": "This file could not be read as a bookmarks file.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Room left on this dashboard: {count}. Pick fewer folders.", + "The import failed; nothing was added.": "The import failed; nothing was added.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bookmark skipped: only web addresses can become tiles", + "{count} bookmarks skipped: only web addresses can become tiles" + ] } } diff --git a/l10n/es.js b/l10n/es.js index 63d8be189..0f0b5e340 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "No se pudieron cargar los atajos de búsqueda.", "The search shortcuts could not be saved." : "No se pudieron guardar los atajos de búsqueda.", "Search {name} for {query}" : "Buscar {query} en {name}", - "Type what to search for in {name}" : "Escribe qué buscar en {name}" + "Type what to search for in {name}" : "Escribe qué buscar en {name}", + "Import bookmarks" : "Importar marcadores", + "Import bookmarks…" : "Importar marcadores…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Elige el archivo de marcadores que exporta tu navegador. Cada carpeta que elijas se convierte en un grupo de mosaicos al final de este panel.", + "Bookmarks file" : "Archivo de marcadores", + "What to import" : "Qué importar", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Este archivo no contiene marcadores.", + "Tiles added: {count}" : "Mosaicos añadidos: {count}", + "Import" : "Importar", + "This file is larger than 5 MB. Export one folder at a time." : "Este archivo ocupa más de 5 MB. Exporta las carpetas de una en una.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Este archivo tiene más de 2.000 marcadores. Exporta las carpetas de una en una.", + "This file could not be read as a bookmarks file." : "Este archivo no se pudo leer como archivo de marcadores.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Espacio que queda en este panel: {count}. Elige menos carpetas.", + "The import failed; nothing was added." : "La importación falló; no se añadió nada.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Se omitió {count} marcador: solo las direcciones web pueden ser mosaicos", "Se omitieron {count} marcadores: solo las direcciones web pueden ser mosaicos"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/es.json b/l10n/es.json index 35b660cdd..1d8b33ccc 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "No se pudieron cargar los atajos de búsqueda.", "The search shortcuts could not be saved.": "No se pudieron guardar los atajos de búsqueda.", "Search {name} for {query}": "Buscar {query} en {name}", - "Type what to search for in {name}": "Escribe qué buscar en {name}" + "Type what to search for in {name}": "Escribe qué buscar en {name}", + "Import bookmarks": "Importar marcadores", + "Import bookmarks…": "Importar marcadores…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Elige el archivo de marcadores que exporta tu navegador. Cada carpeta que elijas se convierte en un grupo de mosaicos al final de este panel.", + "Bookmarks file": "Archivo de marcadores", + "What to import": "Qué importar", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Este archivo no contiene marcadores.", + "Tiles added: {count}": "Mosaicos añadidos: {count}", + "Import": "Importar", + "This file is larger than 5 MB. Export one folder at a time.": "Este archivo ocupa más de 5 MB. Exporta las carpetas de una en una.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Este archivo tiene más de 2.000 marcadores. Exporta las carpetas de una en una.", + "This file could not be read as a bookmarks file.": "Este archivo no se pudo leer como archivo de marcadores.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Espacio que queda en este panel: {count}. Elige menos carpetas.", + "The import failed; nothing was added.": "La importación falló; no se añadió nada.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Se omitió {count} marcador: solo las direcciones web pueden ser mosaicos", + "Se omitieron {count} marcadores: solo las direcciones web pueden ser mosaicos" + ] } } diff --git a/l10n/et.js b/l10n/et.js index 08aaa28d2..1a42808b5 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Otsingu otseteid ei õnnestunud laadida.", "The search shortcuts could not be saved." : "Otsingu otseteid ei õnnestunud salvestada.", "Search {name} for {query}" : "Otsi {name} kaudu: {query}", - "Type what to search for in {name}" : "Sisesta, mida {name} kaudu otsida" + "Type what to search for in {name}" : "Sisesta, mida {name} kaudu otsida", + "Import bookmarks" : "Impordi järjehoidjad", + "Import bookmarks…" : "Impordi järjehoidjad…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Vali järjehoidjate fail, mille su brauser ekspordib. Iga valitud kaust muutub selle töölaua allosas paanide grupiks.", + "Bookmarks file" : "Järjehoidjate fail", + "What to import" : "Mida importida", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Selles failis pole järjehoidjaid.", + "Tiles added: {count}" : "Lisatud paane: {count}", + "Import" : "Impordi", + "This file is larger than 5 MB. Export one folder at a time." : "See fail on suurem kui 5 MB. Ekspordi üks kaust korraga.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Selles failis on üle 2000 järjehoidja. Ekspordi üks kaust korraga.", + "This file could not be read as a bookmarks file." : "Seda faili ei õnnestunud lugeda järjehoidjate failina.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Selle töölaua vaba ruum: {count}. Vali vähem kaustu.", + "The import failed; nothing was added." : "Import ebaõnnestus; midagi ei lisatud.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} järjehoidja jäeti vahele: paaniks saab olla ainult veebiaadress", "{count} järjehoidjat jäeti vahele: paaniks saab olla ainult veebiaadress"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/et.json b/l10n/et.json index d5e64522d..c78794f71 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Otsingu otseteid ei õnnestunud laadida.", "The search shortcuts could not be saved.": "Otsingu otseteid ei õnnestunud salvestada.", "Search {name} for {query}": "Otsi {name} kaudu: {query}", - "Type what to search for in {name}": "Sisesta, mida {name} kaudu otsida" + "Type what to search for in {name}": "Sisesta, mida {name} kaudu otsida", + "Import bookmarks": "Impordi järjehoidjad", + "Import bookmarks…": "Impordi järjehoidjad…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Vali järjehoidjate fail, mille su brauser ekspordib. Iga valitud kaust muutub selle töölaua allosas paanide grupiks.", + "Bookmarks file": "Järjehoidjate fail", + "What to import": "Mida importida", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Selles failis pole järjehoidjaid.", + "Tiles added: {count}": "Lisatud paane: {count}", + "Import": "Impordi", + "This file is larger than 5 MB. Export one folder at a time.": "See fail on suurem kui 5 MB. Ekspordi üks kaust korraga.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Selles failis on üle 2000 järjehoidja. Ekspordi üks kaust korraga.", + "This file could not be read as a bookmarks file.": "Seda faili ei õnnestunud lugeda järjehoidjate failina.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Selle töölaua vaba ruum: {count}. Vali vähem kaustu.", + "The import failed; nothing was added.": "Import ebaõnnestus; midagi ei lisatud.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} järjehoidja jäeti vahele: paaniks saab olla ainult veebiaadress", + "{count} järjehoidjat jäeti vahele: paaniks saab olla ainult veebiaadress" + ] } } diff --git a/l10n/fi.js b/l10n/fi.js index 94bc9489c..c1d45ff71 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Hakuoikoteitä ei voitu ladata.", "The search shortcuts could not be saved." : "Hakuoikoteitä ei voitu tallentaa.", "Search {name} for {query}" : "Hae {name}-palvelusta: {query}", - "Type what to search for in {name}" : "Kirjoita, mitä haetaan palvelusta {name}" + "Type what to search for in {name}" : "Kirjoita, mitä haetaan palvelusta {name}", + "Import bookmarks" : "Tuo kirjanmerkit", + "Import bookmarks…" : "Tuo kirjanmerkit…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Valitse selaimesi viemä kirjanmerkkitiedosto. Jokaisesta valitsemastasi kansiosta tulee ruuturyhmä tämän kojelaudan alaosaan.", + "Bookmarks file" : "Kirjanmerkkitiedosto", + "What to import" : "Mitä tuodaan", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Tässä tiedostossa ei ole kirjanmerkkejä.", + "Tiles added: {count}" : "Lisättyjä ruutuja: {count}", + "Import" : "Tuo", + "This file is larger than 5 MB. Export one folder at a time." : "Tiedosto on yli 5 Mt. Vie yksi kansio kerrallaan.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Tiedostossa on yli 2 000 kirjanmerkkiä. Vie yksi kansio kerrallaan.", + "This file could not be read as a bookmarks file." : "Tiedostoa ei voitu lukea kirjanmerkkitiedostona.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Tilaa jäljellä tällä kojelaudalla: {count}. Valitse vähemmän kansioita.", + "The import failed; nothing was added." : "Tuonti epäonnistui; mitään ei lisätty.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} kirjanmerkki ohitettiin: vain verkko-osoitteista voi tulla ruutuja", "{count} kirjanmerkkiä ohitettiin: vain verkko-osoitteista voi tulla ruutuja"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/fi.json b/l10n/fi.json index fd9488a93..59d8fb40f 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Hakuoikoteitä ei voitu ladata.", "The search shortcuts could not be saved.": "Hakuoikoteitä ei voitu tallentaa.", "Search {name} for {query}": "Hae {name}-palvelusta: {query}", - "Type what to search for in {name}": "Kirjoita, mitä haetaan palvelusta {name}" + "Type what to search for in {name}": "Kirjoita, mitä haetaan palvelusta {name}", + "Import bookmarks": "Tuo kirjanmerkit", + "Import bookmarks…": "Tuo kirjanmerkit…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Valitse selaimesi viemä kirjanmerkkitiedosto. Jokaisesta valitsemastasi kansiosta tulee ruuturyhmä tämän kojelaudan alaosaan.", + "Bookmarks file": "Kirjanmerkkitiedosto", + "What to import": "Mitä tuodaan", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Tässä tiedostossa ei ole kirjanmerkkejä.", + "Tiles added: {count}": "Lisättyjä ruutuja: {count}", + "Import": "Tuo", + "This file is larger than 5 MB. Export one folder at a time.": "Tiedosto on yli 5 Mt. Vie yksi kansio kerrallaan.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Tiedostossa on yli 2 000 kirjanmerkkiä. Vie yksi kansio kerrallaan.", + "This file could not be read as a bookmarks file.": "Tiedostoa ei voitu lukea kirjanmerkkitiedostona.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Tilaa jäljellä tällä kojelaudalla: {count}. Valitse vähemmän kansioita.", + "The import failed; nothing was added.": "Tuonti epäonnistui; mitään ei lisätty.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} kirjanmerkki ohitettiin: vain verkko-osoitteista voi tulla ruutuja", + "{count} kirjanmerkkiä ohitettiin: vain verkko-osoitteista voi tulla ruutuja" + ] } } diff --git a/l10n/fr.js b/l10n/fr.js index c380e5e21..19897425e 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Les raccourcis de recherche n'ont pas pu être chargés.", "The search shortcuts could not be saved." : "Les raccourcis de recherche n'ont pas pu être enregistrés.", "Search {name} for {query}" : "Rechercher {query} dans {name}", - "Type what to search for in {name}" : "Saisissez ce que vous cherchez dans {name}" + "Type what to search for in {name}" : "Saisissez ce que vous cherchez dans {name}", + "Import bookmarks" : "Importer des marque-pages", + "Import bookmarks…" : "Importer des marque-pages…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Choisissez le fichier de marque-pages exporté par votre navigateur. Chaque dossier choisi devient un groupe de tuiles en bas de ce tableau de bord.", + "Bookmarks file" : "Fichier de marque-pages", + "What to import" : "Quoi importer", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ce fichier ne contient aucun marque-page.", + "Tiles added: {count}" : "Tuiles ajoutées : {count}", + "Import" : "Importer", + "This file is larger than 5 MB. Export one folder at a time." : "Ce fichier dépasse 5 Mo. Exportez un dossier à la fois.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ce fichier contient plus de 2 000 marque-pages. Exportez un dossier à la fois.", + "This file could not be read as a bookmarks file." : "Ce fichier n'a pas pu être lu comme fichier de marque-pages.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Place restante sur ce tableau de bord : {count}. Choisissez moins de dossiers.", + "The import failed; nothing was added." : "L'import a échoué ; rien n'a été ajouté.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marque-page ignoré : seules les adresses web peuvent devenir des tuiles", "{count} marque-pages ignorés : seules les adresses web peuvent devenir des tuiles"] }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/fr.json b/l10n/fr.json index fe91c3213..2ede604cd 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Les raccourcis de recherche n'ont pas pu être chargés.", "The search shortcuts could not be saved.": "Les raccourcis de recherche n'ont pas pu être enregistrés.", "Search {name} for {query}": "Rechercher {query} dans {name}", - "Type what to search for in {name}": "Saisissez ce que vous cherchez dans {name}" + "Type what to search for in {name}": "Saisissez ce que vous cherchez dans {name}", + "Import bookmarks": "Importer des marque-pages", + "Import bookmarks…": "Importer des marque-pages…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Choisissez le fichier de marque-pages exporté par votre navigateur. Chaque dossier choisi devient un groupe de tuiles en bas de ce tableau de bord.", + "Bookmarks file": "Fichier de marque-pages", + "What to import": "Quoi importer", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ce fichier ne contient aucun marque-page.", + "Tiles added: {count}": "Tuiles ajoutées : {count}", + "Import": "Importer", + "This file is larger than 5 MB. Export one folder at a time.": "Ce fichier dépasse 5 Mo. Exportez un dossier à la fois.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ce fichier contient plus de 2 000 marque-pages. Exportez un dossier à la fois.", + "This file could not be read as a bookmarks file.": "Ce fichier n'a pas pu être lu comme fichier de marque-pages.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Place restante sur ce tableau de bord : {count}. Choisissez moins de dossiers.", + "The import failed; nothing was added.": "L'import a échoué ; rien n'a été ajouté.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} marque-page ignoré : seules les adresses web peuvent devenir des tuiles", + "{count} marque-pages ignorés : seules les adresses web peuvent devenir des tuiles" + ] } } diff --git a/l10n/ga.js b/l10n/ga.js index 68b2ea329..c1852cdc1 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Níorbh fhéidir na haicearraí cuardaigh a lódáil.", "The search shortcuts could not be saved." : "Níorbh fhéidir na haicearraí cuardaigh a shábháil.", "Search {name} for {query}" : "Cuardaigh {name} le haghaidh {query}", - "Type what to search for in {name}" : "Clóscríobh cad atá le cuardach in {name}" + "Type what to search for in {name}" : "Clóscríobh cad atá le cuardach in {name}", + "Import bookmarks" : "Iompórtáil leabharmharcanna", + "Import bookmarks…" : "Iompórtáil leabharmharcanna…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Roghnaigh an comhad leabharmharcanna a easpórtálann do bhrabhsálaí. Déantar grúpa tíleanna ag bun an deais seo de gach fillteán a roghnaíonn tú.", + "Bookmarks file" : "Comhad leabharmharcanna", + "What to import" : "Cad atá le hiompórtáil", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Níl aon leabharmharcanna sa chomhad seo.", + "Tiles added: {count}" : "Tíleanna curtha leis: {count}", + "Import" : "Iompórtáil", + "This file is larger than 5 MB. Export one folder at a time." : "Tá an comhad seo níos mó ná 5 MB. Easpórtáil fillteán amháin ag an am.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Tá níos mó ná 2,000 leabharmharc sa chomhad seo. Easpórtáil fillteán amháin ag an am.", + "This file could not be read as a bookmarks file." : "Níorbh fhéidir an comhad seo a léamh mar chomhad leabharmharcanna.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Spás fágtha ar an deais seo: {count}. Roghnaigh níos lú fillteán.", + "The import failed; nothing was added." : "Theip ar an iompórtáil; níor cuireadh aon rud leis.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna"] }, "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);" ); diff --git a/l10n/ga.json b/l10n/ga.json index fb5ce194a..88d579d3b 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -1314,6 +1314,27 @@ "The search shortcuts could not be loaded.": "Níorbh fhéidir na haicearraí cuardaigh a lódáil.", "The search shortcuts could not be saved.": "Níorbh fhéidir na haicearraí cuardaigh a shábháil.", "Search {name} for {query}": "Cuardaigh {name} le haghaidh {query}", - "Type what to search for in {name}": "Clóscríobh cad atá le cuardach in {name}" + "Type what to search for in {name}": "Clóscríobh cad atá le cuardach in {name}", + "Import bookmarks": "Iompórtáil leabharmharcanna", + "Import bookmarks…": "Iompórtáil leabharmharcanna…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Roghnaigh an comhad leabharmharcanna a easpórtálann do bhrabhsálaí. Déantar grúpa tíleanna ag bun an deais seo de gach fillteán a roghnaíonn tú.", + "Bookmarks file": "Comhad leabharmharcanna", + "What to import": "Cad atá le hiompórtáil", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Níl aon leabharmharcanna sa chomhad seo.", + "Tiles added: {count}": "Tíleanna curtha leis: {count}", + "Import": "Iompórtáil", + "This file is larger than 5 MB. Export one folder at a time.": "Tá an comhad seo níos mó ná 5 MB. Easpórtáil fillteán amháin ag an am.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Tá níos mó ná 2,000 leabharmharc sa chomhad seo. Easpórtáil fillteán amháin ag an am.", + "This file could not be read as a bookmarks file.": "Níorbh fhéidir an comhad seo a léamh mar chomhad leabharmharcanna.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Spás fágtha ar an deais seo: {count}. Roghnaigh níos lú fillteán.", + "The import failed; nothing was added.": "Theip ar an iompórtáil; níor cuireadh aon rud leis.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", + "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", + "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", + "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", + "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna" + ] } } diff --git a/l10n/hr.js b/l10n/hr.js index 4035bd801..76ec22e66 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Prečace za pretraživanje nije bilo moguće učitati.", "The search shortcuts could not be saved." : "Prečace za pretraživanje nije bilo moguće spremiti.", "Search {name} for {query}" : "Pretraži {name} za {query}", - "Type what to search for in {name}" : "Upišite što tražite u {name}" + "Type what to search for in {name}" : "Upišite što tražite u {name}", + "Import bookmarks" : "Uvezi knjižne oznake", + "Import bookmarks…" : "Uvezi knjižne oznake…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Odaberite datoteku knjižnih oznaka koju izvozi vaš preglednik. Svaka odabrana mapa postaje skupina pločica na dnu ove nadzorne ploče.", + "Bookmarks file" : "Datoteka knjižnih oznaka", + "What to import" : "Što uvesti", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ova datoteka ne sadrži knjižne oznake.", + "Tiles added: {count}" : "Dodano pločica: {count}", + "Import" : "Uvezi", + "This file is larger than 5 MB. Export one folder at a time." : "Ova datoteka je veća od 5 MB. Izvezite jednu po jednu mapu.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ova datoteka ima više od 2.000 knjižnih oznaka. Izvezite jednu po jednu mapu.", + "This file could not be read as a bookmarks file." : "Ova datoteka ne može se pročitati kao datoteka knjižnih oznaka.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Preostalo mjesta na ovoj nadzornoj ploči: {count}. Odaberite manje mapa.", + "The import failed; nothing was added." : "Uvoz nije uspio; ništa nije dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} knjižna oznaka: samo web adrese mogu postati pločice", "Preskočene {count} knjižne oznake: samo web adrese mogu postati pločice", "Preskočeno {count} knjižnih oznaka: samo web adrese mogu postati pločice"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/hr.json b/l10n/hr.json index 8ad434479..fdaaea45d 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Prečace za pretraživanje nije bilo moguće učitati.", "The search shortcuts could not be saved.": "Prečace za pretraživanje nije bilo moguće spremiti.", "Search {name} for {query}": "Pretraži {name} za {query}", - "Type what to search for in {name}": "Upišite što tražite u {name}" + "Type what to search for in {name}": "Upišite što tražite u {name}", + "Import bookmarks": "Uvezi knjižne oznake", + "Import bookmarks…": "Uvezi knjižne oznake…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Odaberite datoteku knjižnih oznaka koju izvozi vaš preglednik. Svaka odabrana mapa postaje skupina pločica na dnu ove nadzorne ploče.", + "Bookmarks file": "Datoteka knjižnih oznaka", + "What to import": "Što uvesti", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ova datoteka ne sadrži knjižne oznake.", + "Tiles added: {count}": "Dodano pločica: {count}", + "Import": "Uvezi", + "This file is larger than 5 MB. Export one folder at a time.": "Ova datoteka je veća od 5 MB. Izvezite jednu po jednu mapu.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ova datoteka ima više od 2.000 knjižnih oznaka. Izvezite jednu po jednu mapu.", + "This file could not be read as a bookmarks file.": "Ova datoteka ne može se pročitati kao datoteka knjižnih oznaka.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Preostalo mjesta na ovoj nadzornoj ploči: {count}. Odaberite manje mapa.", + "The import failed; nothing was added.": "Uvoz nije uspio; ništa nije dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Preskočena {count} knjižna oznaka: samo web adrese mogu postati pločice", + "Preskočene {count} knjižne oznake: samo web adrese mogu postati pločice", + "Preskočeno {count} knjižnih oznaka: samo web adrese mogu postati pločice" + ] } } diff --git a/l10n/hu.js b/l10n/hu.js index 082ef33c8..cdfe8a9b4 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "A keresési parancsikonokat nem sikerült betölteni.", "The search shortcuts could not be saved." : "A keresési parancsikonokat nem sikerült menteni.", "Search {name} for {query}" : "Keresés itt: {name}, erre: {query}", - "Type what to search for in {name}" : "Írd be, mit keresel itt: {name}" + "Type what to search for in {name}" : "Írd be, mit keresel itt: {name}", + "Import bookmarks" : "Könyvjelzők importálása", + "Import bookmarks…" : "Könyvjelzők importálása…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Válaszd ki a böngésződ által exportált könyvjelzőfájlt. Minden kiválasztott mappa csempecsoport lesz ennek az irányítópultnak az alján.", + "Bookmarks file" : "Könyvjelzőfájl", + "What to import" : "Mit importáljunk", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ez a fájl nem tartalmaz könyvjelzőket.", + "Tiles added: {count}" : "Hozzáadott csempék: {count}", + "Import" : "Importálás", + "This file is larger than 5 MB. Export one folder at a time." : "Ez a fájl nagyobb 5 MB-nál. Egyszerre egy mappát exportálj.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ebben a fájlban több mint 2000 könyvjelző van. Egyszerre egy mappát exportálj.", + "This file could not be read as a bookmarks file." : "Ezt a fájlt nem sikerült könyvjelzőfájlként beolvasni.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Szabad hely ezen az irányítópulton: {count}. Válassz kevesebb mappát.", + "The import failed; nothing was added." : "Az importálás sikertelen; semmi sem került hozzáadásra.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} könyvjelző kimaradt: csak webcímekből lehet csempe", "{count} könyvjelző kimaradt: csak webcímekből lehet csempe"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/hu.json b/l10n/hu.json index 53b1313a3..075f7ce4e 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "A keresési parancsikonokat nem sikerült betölteni.", "The search shortcuts could not be saved.": "A keresési parancsikonokat nem sikerült menteni.", "Search {name} for {query}": "Keresés itt: {name}, erre: {query}", - "Type what to search for in {name}": "Írd be, mit keresel itt: {name}" + "Type what to search for in {name}": "Írd be, mit keresel itt: {name}", + "Import bookmarks": "Könyvjelzők importálása", + "Import bookmarks…": "Könyvjelzők importálása…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Válaszd ki a böngésződ által exportált könyvjelzőfájlt. Minden kiválasztott mappa csempecsoport lesz ennek az irányítópultnak az alján.", + "Bookmarks file": "Könyvjelzőfájl", + "What to import": "Mit importáljunk", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ez a fájl nem tartalmaz könyvjelzőket.", + "Tiles added: {count}": "Hozzáadott csempék: {count}", + "Import": "Importálás", + "This file is larger than 5 MB. Export one folder at a time.": "Ez a fájl nagyobb 5 MB-nál. Egyszerre egy mappát exportálj.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ebben a fájlban több mint 2000 könyvjelző van. Egyszerre egy mappát exportálj.", + "This file could not be read as a bookmarks file.": "Ezt a fájlt nem sikerült könyvjelzőfájlként beolvasni.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Szabad hely ezen az irányítópulton: {count}. Válassz kevesebb mappát.", + "The import failed; nothing was added.": "Az importálás sikertelen; semmi sem került hozzáadásra.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} könyvjelző kimaradt: csak webcímekből lehet csempe", + "{count} könyvjelző kimaradt: csak webcímekből lehet csempe" + ] } } diff --git a/l10n/is.js b/l10n/is.js index 4f90573d0..31a01c1d2 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Ekki tókst að hlaða inn leitarflýtileiðum.", "The search shortcuts could not be saved." : "Ekki tókst að vista leitarflýtileiðirnar.", "Search {name} for {query}" : "Leita í {name} að {query}", - "Type what to search for in {name}" : "Sláðu inn hvað á að leita að í {name}" + "Type what to search for in {name}" : "Sláðu inn hvað á að leita að í {name}", + "Import bookmarks" : "Flytja inn bókamerki", + "Import bookmarks…" : "Flytja inn bókamerki…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Veldu bókamerkjaskrána sem vafrinn þinn flytur út. Hver mappa sem þú velur verður hópur reita neðst á þessu stjórnborði.", + "Bookmarks file" : "Bókamerkjaskrá", + "What to import" : "Hvað á að flytja inn", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Þessi skrá inniheldur engin bókamerki.", + "Tiles added: {count}" : "Reitum bætt við: {count}", + "Import" : "Flytja inn", + "This file is larger than 5 MB. Export one folder at a time." : "Þessi skrá er stærri en 5 MB. Flyttu út eina möppu í einu.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Í þessari skrá eru fleiri en 2.000 bókamerki. Flyttu út eina möppu í einu.", + "This file could not be read as a bookmarks file." : "Ekki tókst að lesa þessa skrá sem bókamerkjaskrá.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Pláss eftir á þessu stjórnborði: {count}. Veldu færri möppur.", + "The import failed; nothing was added." : "Innflutningur mistókst; engu var bætt við.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bókamerki sleppt: aðeins vefslóðir geta orðið reitir", "{count} bókamerkjum sleppt: aðeins vefslóðir geta orðið reitir"] }, "nplurals=2; plural=(n%10!=1 || n%100==11);" ); diff --git a/l10n/is.json b/l10n/is.json index ff9642953..5e309bbdd 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Ekki tókst að hlaða inn leitarflýtileiðum.", "The search shortcuts could not be saved.": "Ekki tókst að vista leitarflýtileiðirnar.", "Search {name} for {query}": "Leita í {name} að {query}", - "Type what to search for in {name}": "Sláðu inn hvað á að leita að í {name}" + "Type what to search for in {name}": "Sláðu inn hvað á að leita að í {name}", + "Import bookmarks": "Flytja inn bókamerki", + "Import bookmarks…": "Flytja inn bókamerki…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Veldu bókamerkjaskrána sem vafrinn þinn flytur út. Hver mappa sem þú velur verður hópur reita neðst á þessu stjórnborði.", + "Bookmarks file": "Bókamerkjaskrá", + "What to import": "Hvað á að flytja inn", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Þessi skrá inniheldur engin bókamerki.", + "Tiles added: {count}": "Reitum bætt við: {count}", + "Import": "Flytja inn", + "This file is larger than 5 MB. Export one folder at a time.": "Þessi skrá er stærri en 5 MB. Flyttu út eina möppu í einu.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Í þessari skrá eru fleiri en 2.000 bókamerki. Flyttu út eina möppu í einu.", + "This file could not be read as a bookmarks file.": "Ekki tókst að lesa þessa skrá sem bókamerkjaskrá.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Pláss eftir á þessu stjórnborði: {count}. Veldu færri möppur.", + "The import failed; nothing was added.": "Innflutningur mistókst; engu var bætt við.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bókamerki sleppt: aðeins vefslóðir geta orðið reitir", + "{count} bókamerkjum sleppt: aðeins vefslóðir geta orðið reitir" + ] } } diff --git a/l10n/it.js b/l10n/it.js index 29c3b4ec4..acb6ec296 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Non è stato possibile caricare le scorciatoie di ricerca.", "The search shortcuts could not be saved." : "Non è stato possibile salvare le scorciatoie di ricerca.", "Search {name} for {query}" : "Cerca {query} in {name}", - "Type what to search for in {name}" : "Scrivi cosa cercare in {name}" + "Type what to search for in {name}" : "Scrivi cosa cercare in {name}", + "Import bookmarks" : "Importa segnalibri", + "Import bookmarks…" : "Importa segnalibri…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Scegli il file dei segnalibri esportato dal browser. Ogni cartella scelta diventa un gruppo di riquadri in fondo a questa dashboard.", + "Bookmarks file" : "File dei segnalibri", + "What to import" : "Cosa importare", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Questo file non contiene segnalibri.", + "Tiles added: {count}" : "Riquadri aggiunti: {count}", + "Import" : "Importa", + "This file is larger than 5 MB. Export one folder at a time." : "Questo file supera i 5 MB. Esporta una cartella alla volta.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Questo file contiene più di 2.000 segnalibri. Esporta una cartella alla volta.", + "This file could not be read as a bookmarks file." : "Questo file non può essere letto come file di segnalibri.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Spazio rimasto su questa dashboard: {count}. Scegli meno cartelle.", + "The import failed; nothing was added." : "Importazione non riuscita; non è stato aggiunto nulla.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnalibro saltato: solo gli indirizzi web possono diventare riquadri", "{count} segnalibri saltati: solo gli indirizzi web possono diventare riquadri"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/it.json b/l10n/it.json index a2f5b95cd..c3b0da51c 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Non è stato possibile caricare le scorciatoie di ricerca.", "The search shortcuts could not be saved.": "Non è stato possibile salvare le scorciatoie di ricerca.", "Search {name} for {query}": "Cerca {query} in {name}", - "Type what to search for in {name}": "Scrivi cosa cercare in {name}" + "Type what to search for in {name}": "Scrivi cosa cercare in {name}", + "Import bookmarks": "Importa segnalibri", + "Import bookmarks…": "Importa segnalibri…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Scegli il file dei segnalibri esportato dal browser. Ogni cartella scelta diventa un gruppo di riquadri in fondo a questa dashboard.", + "Bookmarks file": "File dei segnalibri", + "What to import": "Cosa importare", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Questo file non contiene segnalibri.", + "Tiles added: {count}": "Riquadri aggiunti: {count}", + "Import": "Importa", + "This file is larger than 5 MB. Export one folder at a time.": "Questo file supera i 5 MB. Esporta una cartella alla volta.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Questo file contiene più di 2.000 segnalibri. Esporta una cartella alla volta.", + "This file could not be read as a bookmarks file.": "Questo file non può essere letto come file di segnalibri.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Spazio rimasto su questa dashboard: {count}. Scegli meno cartelle.", + "The import failed; nothing was added.": "Importazione non riuscita; non è stato aggiunto nulla.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} segnalibro saltato: solo gli indirizzi web possono diventare riquadri", + "{count} segnalibri saltati: solo gli indirizzi web possono diventare riquadri" + ] } } diff --git a/l10n/lb.js b/l10n/lb.js index 5c290ec2d..8c679fb93 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -1335,7 +1335,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "D'Sich-Ofkierzunge konnten net geluede ginn.", "The search shortcuts could not be saved." : "D'Sich-Ofkierzunge konnten net gespäichert ginn.", "Search {name} for {query}" : "A {name} no {query} sichen", - "Type what to search for in {name}" : "Gëff an, wat an {name} gesicht gëtt" + "Type what to search for in {name}" : "Gëff an, wat an {name} gesicht gëtt", + "Import bookmarks" : "Lieszeechen importéieren", + "Import bookmarks…" : "Lieszeechen importéieren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Wiel d'Lieszeechendatei, déi däi Browser exportéiert. All Dossier, deen s du wiels, gëtt eng Kachelgrupp ënnen op dësem Dashboard.", + "Bookmarks file" : "Lieszeechendatei", + "What to import" : "Wat importéiert gëtt", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Dës Datei huet keng Lieszeechen.", + "Tiles added: {count}" : "Kachelen derbäigesat: {count}", + "Import" : "Importéieren", + "This file is larger than 5 MB. Export one folder at a time." : "Dës Datei ass méi grouss wéi 5 MB. Exportéier een Dossier no deem aneren.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Dës Datei huet méi wéi 2.000 Lieszeechen. Exportéier een Dossier no deem aneren.", + "This file could not be read as a bookmarks file." : "Dës Datei konnt net als Lieszeechendatei gelies ginn.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Plaz op dësem Dashboard: nach {count}. Wiel manner Dossieren.", + "The import failed; nothing was added." : "Den Import ass feelgeschloen; näischt gouf derbäigesat.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn", "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/lb.json b/l10n/lb.json index fc3260785..4b599e1cd 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "D'Sich-Ofkierzunge konnten net geluede ginn.", "The search shortcuts could not be saved.": "D'Sich-Ofkierzunge konnten net gespäichert ginn.", "Search {name} for {query}": "A {name} no {query} sichen", - "Type what to search for in {name}": "Gëff an, wat an {name} gesicht gëtt" + "Type what to search for in {name}": "Gëff an, wat an {name} gesicht gëtt", + "Import bookmarks": "Lieszeechen importéieren", + "Import bookmarks…": "Lieszeechen importéieren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Wiel d'Lieszeechendatei, déi däi Browser exportéiert. All Dossier, deen s du wiels, gëtt eng Kachelgrupp ënnen op dësem Dashboard.", + "Bookmarks file": "Lieszeechendatei", + "What to import": "Wat importéiert gëtt", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Dës Datei huet keng Lieszeechen.", + "Tiles added: {count}": "Kachelen derbäigesat: {count}", + "Import": "Importéieren", + "This file is larger than 5 MB. Export one folder at a time.": "Dës Datei ass méi grouss wéi 5 MB. Exportéier een Dossier no deem aneren.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Dës Datei huet méi wéi 2.000 Lieszeechen. Exportéier een Dossier no deem aneren.", + "This file could not be read as a bookmarks file.": "Dës Datei konnt net als Lieszeechendatei gelies ginn.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Plaz op dësem Dashboard: nach {count}. Wiel manner Dossieren.", + "The import failed; nothing was added.": "Den Import ass feelgeschloen; näischt gouf derbäigesat.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn", + "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn" + ] } } diff --git a/l10n/lt.js b/l10n/lt.js index 6cae31274..90570facd 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Nepavyko įkelti paieškos nuorodų.", "The search shortcuts could not be saved." : "Nepavyko išsaugoti paieškos nuorodų.", "Search {name} for {query}" : "Ieškoti {name}: {query}", - "Type what to search for in {name}" : "Įveskite, ko ieškoti {name}" + "Type what to search for in {name}" : "Įveskite, ko ieškoti {name}", + "Import bookmarks" : "Importuoti žymes", + "Import bookmarks…" : "Importuoti žymes…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Pasirinkite žymių failą, kurį eksportuoja jūsų naršyklė. Kiekvienas pasirinktas aplankas tampa plytelių grupe šios skydelio apačioje.", + "Bookmarks file" : "Žymių failas", + "What to import" : "Ką importuoti", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Šiame faile žymių nėra.", + "Tiles added: {count}" : "Pridėta plytelių: {count}", + "Import" : "Importuoti", + "This file is larger than 5 MB. Export one folder at a time." : "Šis failas didesnis nei 5 MB. Eksportuokite po vieną aplanką.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Šiame faile yra daugiau nei 2 000 žymių. Eksportuokite po vieną aplanką.", + "This file could not be read as a bookmarks file." : "Šio failo nepavyko perskaityti kaip žymių failo.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Laisvos vietos šiame skydelyje: {count}. Pasirinkite mažiau aplankų.", + "The import failed; nothing was added." : "Importuoti nepavyko; nieko nepridėta.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Praleista {count} žymė: plytele gali tapti tik žiniatinklio adresas", "Praleistos {count} žymės: plytele gali tapti tik žiniatinklio adresas", "Praleista {count} žymių: plytele gali tapti tik žiniatinklio adresas"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/lt.json b/l10n/lt.json index a74b6a426..7fb79f50b 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Nepavyko įkelti paieškos nuorodų.", "The search shortcuts could not be saved.": "Nepavyko išsaugoti paieškos nuorodų.", "Search {name} for {query}": "Ieškoti {name}: {query}", - "Type what to search for in {name}": "Įveskite, ko ieškoti {name}" + "Type what to search for in {name}": "Įveskite, ko ieškoti {name}", + "Import bookmarks": "Importuoti žymes", + "Import bookmarks…": "Importuoti žymes…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Pasirinkite žymių failą, kurį eksportuoja jūsų naršyklė. Kiekvienas pasirinktas aplankas tampa plytelių grupe šios skydelio apačioje.", + "Bookmarks file": "Žymių failas", + "What to import": "Ką importuoti", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Šiame faile žymių nėra.", + "Tiles added: {count}": "Pridėta plytelių: {count}", + "Import": "Importuoti", + "This file is larger than 5 MB. Export one folder at a time.": "Šis failas didesnis nei 5 MB. Eksportuokite po vieną aplanką.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Šiame faile yra daugiau nei 2 000 žymių. Eksportuokite po vieną aplanką.", + "This file could not be read as a bookmarks file.": "Šio failo nepavyko perskaityti kaip žymių failo.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Laisvos vietos šiame skydelyje: {count}. Pasirinkite mažiau aplankų.", + "The import failed; nothing was added.": "Importuoti nepavyko; nieko nepridėta.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Praleista {count} žymė: plytele gali tapti tik žiniatinklio adresas", + "Praleistos {count} žymės: plytele gali tapti tik žiniatinklio adresas", + "Praleista {count} žymių: plytele gali tapti tik žiniatinklio adresas" + ] } } diff --git a/l10n/lv.js b/l10n/lv.js index f2fb40691..17bfa781e 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Neizdevās ielādēt meklēšanas saīsnes.", "The search shortcuts could not be saved." : "Neizdevās saglabāt meklēšanas saīsnes.", "Search {name} for {query}" : "Meklēt {name}: {query}", - "Type what to search for in {name}" : "Ierakstiet, ko meklēt {name}" + "Type what to search for in {name}" : "Ierakstiet, ko meklēt {name}", + "Import bookmarks" : "Importēt grāmatzīmes", + "Import bookmarks…" : "Importēt grāmatzīmes…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Izvēlieties grāmatzīmju failu, ko eksportē jūsu pārlūks. Katra izvēlētā mape kļūst par flīžu grupu šī paneļa apakšā.", + "Bookmarks file" : "Grāmatzīmju fails", + "What to import" : "Ko importēt", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Šajā failā nav grāmatzīmju.", + "Tiles added: {count}" : "Pievienotas flīzes: {count}", + "Import" : "Importēt", + "This file is larger than 5 MB. Export one folder at a time." : "Šis fails ir lielāks par 5 MB. Eksportējiet pa vienai mapei.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Šajā failā ir vairāk nekā 2000 grāmatzīmju. Eksportējiet pa vienai mapei.", + "This file could not be read as a bookmarks file." : "Šo failu neizdevās nolasīt kā grāmatzīmju failu.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Brīvā vieta šajā panelī: {count}. Izvēlieties mazāk mapju.", + "The import failed; nothing was added." : "Importēšana neizdevās; nekas netika pievienots.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaistas {count} grāmatzīmes: par flīzi var kļūt tikai tīmekļa adrese"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" ); diff --git a/l10n/lv.json b/l10n/lv.json index aa43ec05c..6a8cbbf36 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Neizdevās ielādēt meklēšanas saīsnes.", "The search shortcuts could not be saved.": "Neizdevās saglabāt meklēšanas saīsnes.", "Search {name} for {query}": "Meklēt {name}: {query}", - "Type what to search for in {name}": "Ierakstiet, ko meklēt {name}" + "Type what to search for in {name}": "Ierakstiet, ko meklēt {name}", + "Import bookmarks": "Importēt grāmatzīmes", + "Import bookmarks…": "Importēt grāmatzīmes…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Izvēlieties grāmatzīmju failu, ko eksportē jūsu pārlūks. Katra izvēlētā mape kļūst par flīžu grupu šī paneļa apakšā.", + "Bookmarks file": "Grāmatzīmju fails", + "What to import": "Ko importēt", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Šajā failā nav grāmatzīmju.", + "Tiles added: {count}": "Pievienotas flīzes: {count}", + "Import": "Importēt", + "This file is larger than 5 MB. Export one folder at a time.": "Šis fails ir lielāks par 5 MB. Eksportējiet pa vienai mapei.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Šajā failā ir vairāk nekā 2000 grāmatzīmju. Eksportējiet pa vienai mapei.", + "This file could not be read as a bookmarks file.": "Šo failu neizdevās nolasīt kā grāmatzīmju failu.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Brīvā vieta šajā panelī: {count}. Izvēlieties mazāk mapju.", + "The import failed; nothing was added.": "Importēšana neizdevās; nekas netika pievienots.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", + "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", + "Izlaistas {count} grāmatzīmes: par flīzi var kļūt tikai tīmekļa adrese" + ] } } diff --git a/l10n/mk.js b/l10n/mk.js index 7362e3b7c..5a9bab9ab 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -1335,7 +1335,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Кратенките за пребарување не можеа да се вчитаат.", "The search shortcuts could not be saved." : "Кратенките за пребарување не можеа да се зачуваат.", "Search {name} for {query}" : "Пребарај во {name} за {query}", - "Type what to search for in {name}" : "Внесете што да се бара во {name}" + "Type what to search for in {name}" : "Внесете што да се бара во {name}", + "Import bookmarks" : "Увези обележувачи", + "Import bookmarks…" : "Увези обележувачи…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Изберете ја датотеката со обележувачи што ја извезува вашиот прелистувач. Секоја избрана папка станува група плочки на дното на оваа контролна табла.", + "Bookmarks file" : "Датотека со обележувачи", + "What to import" : "Што да се увезе", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Оваа датотека нема обележувачи.", + "Tiles added: {count}" : "Додадени плочки: {count}", + "Import" : "Увези", + "This file is larger than 5 MB. Export one folder at a time." : "Оваа датотека е поголема од 5 MB. Извезувајте по една папка.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Оваа датотека има повеќе од 2.000 обележувачи. Извезувајте по една папка.", + "This file could not be read as a bookmarks file." : "Оваа датотека не може да се прочита како датотека со обележувачи.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Преостанато место на оваа табла: {count}. Изберете помалку папки.", + "The import failed; nothing was added." : "Увезувањето не успеа; ништо не е додадено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескокнат {count} обележувач: само веб-адреси можат да станат плочки", "Прескокнати {count} обележувачи: само веб-адреси можат да станат плочки"] }, "nplurals=2; plural=(n%10==1 && n%100!=11 ? 0 : 1);" ); diff --git a/l10n/mk.json b/l10n/mk.json index fb1125f6f..f01e1051c 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Кратенките за пребарување не можеа да се вчитаат.", "The search shortcuts could not be saved.": "Кратенките за пребарување не можеа да се зачуваат.", "Search {name} for {query}": "Пребарај во {name} за {query}", - "Type what to search for in {name}": "Внесете што да се бара во {name}" + "Type what to search for in {name}": "Внесете што да се бара во {name}", + "Import bookmarks": "Увези обележувачи", + "Import bookmarks…": "Увези обележувачи…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Изберете ја датотеката со обележувачи што ја извезува вашиот прелистувач. Секоја избрана папка станува група плочки на дното на оваа контролна табла.", + "Bookmarks file": "Датотека со обележувачи", + "What to import": "Што да се увезе", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Оваа датотека нема обележувачи.", + "Tiles added: {count}": "Додадени плочки: {count}", + "Import": "Увези", + "This file is larger than 5 MB. Export one folder at a time.": "Оваа датотека е поголема од 5 MB. Извезувајте по една папка.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Оваа датотека има повеќе од 2.000 обележувачи. Извезувајте по една папка.", + "This file could not be read as a bookmarks file.": "Оваа датотека не може да се прочита како датотека со обележувачи.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Преостанато место на оваа табла: {count}. Изберете помалку папки.", + "The import failed; nothing was added.": "Увезувањето не успеа; ништо не е додадено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Прескокнат {count} обележувач: само веб-адреси можат да станат плочки", + "Прескокнати {count} обележувачи: само веб-адреси можат да станат плочки" + ] } } diff --git a/l10n/mt.js b/l10n/mt.js index 27d0a5338..b1aafa4d6 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Il-shortcuts tat-tfittxija ma setgħux jitgħabbew.", "The search shortcuts could not be saved." : "Il-shortcuts tat-tfittxija ma setgħux jiġu salvati.", "Search {name} for {query}" : "Fittex {name} għal {query}", - "Type what to search for in {name}" : "Ikteb x'tixtieq tfittex f'{name}" + "Type what to search for in {name}" : "Ikteb x'tixtieq tfittex f'{name}", + "Import bookmarks" : "Importa l-bookmarks", + "Import bookmarks…" : "Importa l-bookmarks…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Agħżel il-fajl tal-bookmarks li jesporta l-browser tiegħek. Kull folder li tagħżel isir grupp ta' madum fil-qiegħ ta' dan id-dashboard.", + "Bookmarks file" : "Fajl tal-bookmarks", + "What to import" : "X'għandu jiġi importat", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Dan il-fajl m'għandux bookmarks.", + "Tiles added: {count}" : "Madum miżjud: {count}", + "Import" : "Importa", + "This file is larger than 5 MB. Export one folder at a time." : "Dan il-fajl huwa akbar minn 5 MB. Esporta folder wieħed kull darba.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Dan il-fajl għandu aktar minn 2,000 bookmark. Esporta folder wieħed kull darba.", + "This file could not be read as a bookmarks file." : "Dan il-fajl ma setax jinqara bħala fajl tal-bookmarks.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Spazju li fadal f'dan id-dashboard: {count}. Agħżel inqas folders.", + "The import failed; nothing was added." : "L-importazzjoni falliet; ma żdied xejn.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum"] }, "nplurals=4; plural=(n==1 ? 0 : n==0 || (n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20) ? 2 : 3);" ); diff --git a/l10n/mt.json b/l10n/mt.json index 0b718764f..05cab7037 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -1313,6 +1313,26 @@ "The search shortcuts could not be loaded.": "Il-shortcuts tat-tfittxija ma setgħux jitgħabbew.", "The search shortcuts could not be saved.": "Il-shortcuts tat-tfittxija ma setgħux jiġu salvati.", "Search {name} for {query}": "Fittex {name} għal {query}", - "Type what to search for in {name}": "Ikteb x'tixtieq tfittex f'{name}" + "Type what to search for in {name}": "Ikteb x'tixtieq tfittex f'{name}", + "Import bookmarks": "Importa l-bookmarks", + "Import bookmarks…": "Importa l-bookmarks…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Agħżel il-fajl tal-bookmarks li jesporta l-browser tiegħek. Kull folder li tagħżel isir grupp ta' madum fil-qiegħ ta' dan id-dashboard.", + "Bookmarks file": "Fajl tal-bookmarks", + "What to import": "X'għandu jiġi importat", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Dan il-fajl m'għandux bookmarks.", + "Tiles added: {count}": "Madum miżjud: {count}", + "Import": "Importa", + "This file is larger than 5 MB. Export one folder at a time.": "Dan il-fajl huwa akbar minn 5 MB. Esporta folder wieħed kull darba.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Dan il-fajl għandu aktar minn 2,000 bookmark. Esporta folder wieħed kull darba.", + "This file could not be read as a bookmarks file.": "Dan il-fajl ma setax jinqara bħala fajl tal-bookmarks.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Spazju li fadal f'dan id-dashboard: {count}. Agħżel inqas folders.", + "The import failed; nothing was added.": "L-importazzjoni falliet; ma żdied xejn.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", + "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", + "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", + "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum" + ] } } diff --git a/l10n/nb.js b/l10n/nb.js index 99e2066b9..4112efb8a 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Søkesnarveiene kunne ikke lastes inn.", "The search shortcuts could not be saved." : "Søkesnarveiene kunne ikke lagres.", "Search {name} for {query}" : "Søk i {name} etter {query}", - "Type what to search for in {name}" : "Skriv hva du vil søke etter i {name}" + "Type what to search for in {name}" : "Skriv hva du vil søke etter i {name}", + "Import bookmarks" : "Importer bokmerker", + "Import bookmarks…" : "Importer bokmerker…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Velg bokmerkefilen nettleseren din eksporterer. Hver mappe du velger blir en gruppe fliser nederst på dette dashbordet.", + "Bookmarks file" : "Bokmerkefil", + "What to import" : "Hva som skal importeres", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Denne filen inneholder ingen bokmerker.", + "Tiles added: {count}" : "Fliser lagt til: {count}", + "Import" : "Importer", + "This file is larger than 5 MB. Export one folder at a time." : "Denne filen er større enn 5 MB. Eksporter én mappe om gangen.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Denne filen har mer enn 2 000 bokmerker. Eksporter én mappe om gangen.", + "This file could not be read as a bookmarks file." : "Denne filen kunne ikke leses som en bokmerkefil.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Plass igjen på dette dashbordet: {count}. Velg færre mapper.", + "The import failed; nothing was added." : "Importen mislyktes; ingenting ble lagt til.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmerke hoppet over: bare nettadresser kan bli fliser", "{count} bokmerker hoppet over: bare nettadresser kan bli fliser"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nb.json b/l10n/nb.json index e21520921..3827381b1 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Søkesnarveiene kunne ikke lastes inn.", "The search shortcuts could not be saved.": "Søkesnarveiene kunne ikke lagres.", "Search {name} for {query}": "Søk i {name} etter {query}", - "Type what to search for in {name}": "Skriv hva du vil søke etter i {name}" + "Type what to search for in {name}": "Skriv hva du vil søke etter i {name}", + "Import bookmarks": "Importer bokmerker", + "Import bookmarks…": "Importer bokmerker…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Velg bokmerkefilen nettleseren din eksporterer. Hver mappe du velger blir en gruppe fliser nederst på dette dashbordet.", + "Bookmarks file": "Bokmerkefil", + "What to import": "Hva som skal importeres", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Denne filen inneholder ingen bokmerker.", + "Tiles added: {count}": "Fliser lagt til: {count}", + "Import": "Importer", + "This file is larger than 5 MB. Export one folder at a time.": "Denne filen er større enn 5 MB. Eksporter én mappe om gangen.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Denne filen har mer enn 2 000 bokmerker. Eksporter én mappe om gangen.", + "This file could not be read as a bookmarks file.": "Denne filen kunne ikke leses som en bokmerkefil.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Plass igjen på dette dashbordet: {count}. Velg færre mapper.", + "The import failed; nothing was added.": "Importen mislyktes; ingenting ble lagt til.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bokmerke hoppet over: bare nettadresser kan bli fliser", + "{count} bokmerker hoppet over: bare nettadresser kan bli fliser" + ] } } diff --git a/l10n/nl.js b/l10n/nl.js index 106de86b2..02b167e0a 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -1394,7 +1394,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "De zoeksnelkoppelingen konden niet worden geladen.", "The search shortcuts could not be saved." : "De zoeksnelkoppelingen konden niet worden opgeslagen.", "Search {name} for {query}" : "Zoek in {name} naar {query}", - "Type what to search for in {name}" : "Typ waarnaar je in {name} wilt zoeken" + "Type what to search for in {name}" : "Typ waarnaar je in {name} wilt zoeken", + "Import bookmarks" : "Bladwijzers importeren", + "Import bookmarks…" : "Bladwijzers importeren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Kies het bladwijzerbestand dat je browser exporteert. Elke map die je kiest wordt een groep tegels onderaan dit dashboard.", + "Bookmarks file" : "Bladwijzerbestand", + "What to import" : "Wat importeren", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Dit bestand bevat geen bladwijzers.", + "Tiles added: {count}" : "Tegels toegevoegd: {count}", + "Import" : "Importeren", + "This file is larger than 5 MB. Export one folder at a time." : "Dit bestand is groter dan 5 MB. Exporteer één map tegelijk.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Dit bestand heeft meer dan 2.000 bladwijzers. Exporteer één map tegelijk.", + "This file could not be read as a bookmarks file." : "Dit bestand kon niet als bladwijzerbestand worden gelezen.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Ruimte over op dit dashboard: {count}. Kies minder mappen.", + "The import failed; nothing was added." : "Het importeren is mislukt; er is niets toegevoegd.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bladwijzer overgeslagen: alleen webadressen kunnen een tegel worden", "{count} bladwijzers overgeslagen: alleen webadressen kunnen een tegel worden"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nl.json b/l10n/nl.json index 151627e67..022449bad 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -1371,6 +1371,24 @@ "The search shortcuts could not be loaded.": "De zoeksnelkoppelingen konden niet worden geladen.", "The search shortcuts could not be saved.": "De zoeksnelkoppelingen konden niet worden opgeslagen.", "Search {name} for {query}": "Zoek in {name} naar {query}", - "Type what to search for in {name}": "Typ waarnaar je in {name} wilt zoeken" + "Type what to search for in {name}": "Typ waarnaar je in {name} wilt zoeken", + "Import bookmarks": "Bladwijzers importeren", + "Import bookmarks…": "Bladwijzers importeren…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Kies het bladwijzerbestand dat je browser exporteert. Elke map die je kiest wordt een groep tegels onderaan dit dashboard.", + "Bookmarks file": "Bladwijzerbestand", + "What to import": "Wat importeren", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Dit bestand bevat geen bladwijzers.", + "Tiles added: {count}": "Tegels toegevoegd: {count}", + "Import": "Importeren", + "This file is larger than 5 MB. Export one folder at a time.": "Dit bestand is groter dan 5 MB. Exporteer één map tegelijk.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Dit bestand heeft meer dan 2.000 bladwijzers. Exporteer één map tegelijk.", + "This file could not be read as a bookmarks file.": "Dit bestand kon niet als bladwijzerbestand worden gelezen.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Ruimte over op dit dashboard: {count}. Kies minder mappen.", + "The import failed; nothing was added.": "Het importeren is mislukt; er is niets toegevoegd.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bladwijzer overgeslagen: alleen webadressen kunnen een tegel worden", + "{count} bladwijzers overgeslagen: alleen webadressen kunnen een tegel worden" + ] } } diff --git a/l10n/pl.js b/l10n/pl.js index e92140d87..605c71a94 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Nie udało się wczytać skrótów wyszukiwania.", "The search shortcuts could not be saved." : "Nie udało się zapisać skrótów wyszukiwania.", "Search {name} for {query}" : "Szukaj w {name}: {query}", - "Type what to search for in {name}" : "Wpisz, czego szukać w {name}" + "Type what to search for in {name}" : "Wpisz, czego szukać w {name}", + "Import bookmarks" : "Importuj zakładki", + "Import bookmarks…" : "Importuj zakładki…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Wybierz plik zakładek eksportowany przez przeglądarkę. Każdy wybrany folder stanie się grupą kafelków na dole tego pulpitu.", + "Bookmarks file" : "Plik zakładek", + "What to import" : "Co zaimportować", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ten plik nie zawiera zakładek.", + "Tiles added: {count}" : "Dodane kafelki: {count}", + "Import" : "Importuj", + "This file is larger than 5 MB. Export one folder at a time." : "Ten plik jest większy niż 5 MB. Eksportuj po jednym folderze.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ten plik ma ponad 2000 zakładek. Eksportuj po jednym folderze.", + "This file could not be read as a bookmarks file." : "Nie udało się odczytać tego pliku jako pliku zakładek.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Wolne miejsce na tym pulpicie: {count}. Wybierz mniej folderów.", + "The import failed; nothing was added." : "Import nie powiódł się; nic nie dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Pominięto {count} zakładkę: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładki: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładek: kafelkiem może zostać tylko adres internetowy"] }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/pl.json b/l10n/pl.json index 9c36922cd..fbd69e43d 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Nie udało się wczytać skrótów wyszukiwania.", "The search shortcuts could not be saved.": "Nie udało się zapisać skrótów wyszukiwania.", "Search {name} for {query}": "Szukaj w {name}: {query}", - "Type what to search for in {name}": "Wpisz, czego szukać w {name}" + "Type what to search for in {name}": "Wpisz, czego szukać w {name}", + "Import bookmarks": "Importuj zakładki", + "Import bookmarks…": "Importuj zakładki…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Wybierz plik zakładek eksportowany przez przeglądarkę. Każdy wybrany folder stanie się grupą kafelków na dole tego pulpitu.", + "Bookmarks file": "Plik zakładek", + "What to import": "Co zaimportować", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ten plik nie zawiera zakładek.", + "Tiles added: {count}": "Dodane kafelki: {count}", + "Import": "Importuj", + "This file is larger than 5 MB. Export one folder at a time.": "Ten plik jest większy niż 5 MB. Eksportuj po jednym folderze.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ten plik ma ponad 2000 zakładek. Eksportuj po jednym folderze.", + "This file could not be read as a bookmarks file.": "Nie udało się odczytać tego pliku jako pliku zakładek.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Wolne miejsce na tym pulpicie: {count}. Wybierz mniej folderów.", + "The import failed; nothing was added.": "Import nie powiódł się; nic nie dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Pominięto {count} zakładkę: kafelkiem może zostać tylko adres internetowy", + "Pominięto {count} zakładki: kafelkiem może zostać tylko adres internetowy", + "Pominięto {count} zakładek: kafelkiem może zostać tylko adres internetowy" + ] } } diff --git a/l10n/pt.js b/l10n/pt.js index 015fafad2..fa6ca804c 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Não foi possível carregar os atalhos de pesquisa.", "The search shortcuts could not be saved." : "Não foi possível guardar os atalhos de pesquisa.", "Search {name} for {query}" : "Pesquisar {query} em {name}", - "Type what to search for in {name}" : "Escreva o que pesquisar em {name}" + "Type what to search for in {name}" : "Escreva o que pesquisar em {name}", + "Import bookmarks" : "Importar marcadores", + "Import bookmarks…" : "Importar marcadores…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Escolha o ficheiro de marcadores que o seu navegador exporta. Cada pasta que escolher torna-se um grupo de mosaicos no fundo deste painel.", + "Bookmarks file" : "Ficheiro de marcadores", + "What to import" : "O que importar", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Este ficheiro não contém marcadores.", + "Tiles added: {count}" : "Mosaicos adicionados: {count}", + "Import" : "Importar", + "This file is larger than 5 MB. Export one folder at a time." : "Este ficheiro tem mais de 5 MB. Exporte uma pasta de cada vez.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Este ficheiro tem mais de 2000 marcadores. Exporte uma pasta de cada vez.", + "This file could not be read as a bookmarks file." : "Não foi possível ler este ficheiro como ficheiro de marcadores.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Espaço restante neste painel: {count}. Escolha menos pastas.", + "The import failed; nothing was added." : "A importação falhou; nada foi adicionado.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcador ignorado: só endereços web podem ser mosaicos", "{count} marcadores ignorados: só endereços web podem ser mosaicos"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/pt.json b/l10n/pt.json index 8d6217dfd..c309aaf4f 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Não foi possível carregar os atalhos de pesquisa.", "The search shortcuts could not be saved.": "Não foi possível guardar os atalhos de pesquisa.", "Search {name} for {query}": "Pesquisar {query} em {name}", - "Type what to search for in {name}": "Escreva o que pesquisar em {name}" + "Type what to search for in {name}": "Escreva o que pesquisar em {name}", + "Import bookmarks": "Importar marcadores", + "Import bookmarks…": "Importar marcadores…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Escolha o ficheiro de marcadores que o seu navegador exporta. Cada pasta que escolher torna-se um grupo de mosaicos no fundo deste painel.", + "Bookmarks file": "Ficheiro de marcadores", + "What to import": "O que importar", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Este ficheiro não contém marcadores.", + "Tiles added: {count}": "Mosaicos adicionados: {count}", + "Import": "Importar", + "This file is larger than 5 MB. Export one folder at a time.": "Este ficheiro tem mais de 5 MB. Exporte uma pasta de cada vez.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Este ficheiro tem mais de 2000 marcadores. Exporte uma pasta de cada vez.", + "This file could not be read as a bookmarks file.": "Não foi possível ler este ficheiro como ficheiro de marcadores.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Espaço restante neste painel: {count}. Escolha menos pastas.", + "The import failed; nothing was added.": "A importação falhou; nada foi adicionado.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} marcador ignorado: só endereços web podem ser mosaicos", + "{count} marcadores ignorados: só endereços web podem ser mosaicos" + ] } } diff --git a/l10n/rm.js b/l10n/rm.js index f77db960b..cf2621461 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "I n'è betg reussì da chargiar las scursanidas da tschertga.", "The search shortcuts could not be saved." : "I n'è betg reussì da memorisar las scursanidas da tschertga.", "Search {name} for {query}" : "Tschertgar en {name} suenter {query}", - "Type what to search for in {name}" : "Endatescha tge tschertgar en {name}" + "Type what to search for in {name}" : "Endatescha tge tschertgar en {name}", + "Import bookmarks" : "Importar segnapaginas", + "Import bookmarks…" : "Importar segnapaginas…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Tscherna la datoteca da segnapaginas che tes navigatur exporta. Mintga ordinatur che ti tschernas daventa ina gruppa da quadrels sut sin quest dashboard.", + "Bookmarks file" : "Datoteca da segnapaginas", + "What to import" : "Tge importar", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Questa datoteca na cuntegna nagins segnapaginas.", + "Tiles added: {count}" : "Quadrels agiuntads: {count}", + "Import" : "Importar", + "This file is larger than 5 MB. Export one folder at a time." : "Questa datoteca è pli gronda che 5 MB. Exportescha in ordinatur a la giada.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Questa datoteca ha dapli che 2000 segnapaginas. Exportescha in ordinatur a la giada.", + "This file could not be read as a bookmarks file." : "Questa datoteca na po betg vegnir legida sco datoteca da segnapaginas.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Plaz restanta sin quest dashboard: {count}. Tscherna main ordinaturs.", + "The import failed; nothing was added." : "L'import n'è betg reussì; nagut n'è vegnì agiuntà.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnapaginas sursiglì: mo adressas web pon daventar quadrels", "{count} segnapaginas sursiglids: mo adressas web pon daventar quadrels"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/rm.json b/l10n/rm.json index 2b6876037..6deef3e07 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "I n'è betg reussì da chargiar las scursanidas da tschertga.", "The search shortcuts could not be saved.": "I n'è betg reussì da memorisar las scursanidas da tschertga.", "Search {name} for {query}": "Tschertgar en {name} suenter {query}", - "Type what to search for in {name}": "Endatescha tge tschertgar en {name}" + "Type what to search for in {name}": "Endatescha tge tschertgar en {name}", + "Import bookmarks": "Importar segnapaginas", + "Import bookmarks…": "Importar segnapaginas…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Tscherna la datoteca da segnapaginas che tes navigatur exporta. Mintga ordinatur che ti tschernas daventa ina gruppa da quadrels sut sin quest dashboard.", + "Bookmarks file": "Datoteca da segnapaginas", + "What to import": "Tge importar", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Questa datoteca na cuntegna nagins segnapaginas.", + "Tiles added: {count}": "Quadrels agiuntads: {count}", + "Import": "Importar", + "This file is larger than 5 MB. Export one folder at a time.": "Questa datoteca è pli gronda che 5 MB. Exportescha in ordinatur a la giada.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Questa datoteca ha dapli che 2000 segnapaginas. Exportescha in ordinatur a la giada.", + "This file could not be read as a bookmarks file.": "Questa datoteca na po betg vegnir legida sco datoteca da segnapaginas.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Plaz restanta sin quest dashboard: {count}. Tscherna main ordinaturs.", + "The import failed; nothing was added.": "L'import n'è betg reussì; nagut n'è vegnì agiuntà.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} segnapaginas sursiglì: mo adressas web pon daventar quadrels", + "{count} segnapaginas sursiglids: mo adressas web pon daventar quadrels" + ] } } diff --git a/l10n/ro.js b/l10n/ro.js index 592be2a0f..2741eca68 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Scurtăturile de căutare nu au putut fi încărcate.", "The search shortcuts could not be saved." : "Scurtăturile de căutare nu au putut fi salvate.", "Search {name} for {query}" : "Caută în {name} după {query}", - "Type what to search for in {name}" : "Scrie ce să cauți în {name}" + "Type what to search for in {name}" : "Scrie ce să cauți în {name}", + "Import bookmarks" : "Importă marcaje", + "Import bookmarks…" : "Importă marcaje…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Alege fișierul de marcaje exportat de browser. Fiecare dosar ales devine un grup de dale în partea de jos a acestui tablou de bord.", + "Bookmarks file" : "Fișier de marcaje", + "What to import" : "Ce să importăm", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Acest fișier nu conține marcaje.", + "Tiles added: {count}" : "Dale adăugate: {count}", + "Import" : "Importă", + "This file is larger than 5 MB. Export one folder at a time." : "Acest fișier depășește 5 MB. Exportă câte un dosar.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Acest fișier are peste 2.000 de marcaje. Exportă câte un dosar.", + "This file could not be read as a bookmarks file." : "Acest fișier nu a putut fi citit ca fișier de marcaje.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Spațiu rămas pe acest tablou de bord: {count}. Alege mai puține dosare.", + "The import failed; nothing was added." : "Importul a eșuat; nu s-a adăugat nimic.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcaj omis: doar adresele web pot deveni dale", "{count} marcaje omise: doar adresele web pot deveni dale", "{count} de marcaje omise: doar adresele web pot deveni dale"] }, "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);" ); diff --git a/l10n/ro.json b/l10n/ro.json index 1df78b4ee..7bcda640a 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Scurtăturile de căutare nu au putut fi încărcate.", "The search shortcuts could not be saved.": "Scurtăturile de căutare nu au putut fi salvate.", "Search {name} for {query}": "Caută în {name} după {query}", - "Type what to search for in {name}": "Scrie ce să cauți în {name}" + "Type what to search for in {name}": "Scrie ce să cauți în {name}", + "Import bookmarks": "Importă marcaje", + "Import bookmarks…": "Importă marcaje…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Alege fișierul de marcaje exportat de browser. Fiecare dosar ales devine un grup de dale în partea de jos a acestui tablou de bord.", + "Bookmarks file": "Fișier de marcaje", + "What to import": "Ce să importăm", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Acest fișier nu conține marcaje.", + "Tiles added: {count}": "Dale adăugate: {count}", + "Import": "Importă", + "This file is larger than 5 MB. Export one folder at a time.": "Acest fișier depășește 5 MB. Exportă câte un dosar.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Acest fișier are peste 2.000 de marcaje. Exportă câte un dosar.", + "This file could not be read as a bookmarks file.": "Acest fișier nu a putut fi citit ca fișier de marcaje.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Spațiu rămas pe acest tablou de bord: {count}. Alege mai puține dosare.", + "The import failed; nothing was added.": "Importul a eșuat; nu s-a adăugat nimic.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} marcaj omis: doar adresele web pot deveni dale", + "{count} marcaje omise: doar adresele web pot deveni dale", + "{count} de marcaje omise: doar adresele web pot deveni dale" + ] } } diff --git a/l10n/ru.js b/l10n/ru.js index 114a48973..723a7e949 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Не удалось загрузить ярлыки поиска.", "The search shortcuts could not be saved." : "Не удалось сохранить ярлыки поиска.", "Search {name} for {query}" : "Искать в {name}: {query}", - "Type what to search for in {name}" : "Введите, что искать в {name}" + "Type what to search for in {name}" : "Введите, что искать в {name}", + "Import bookmarks" : "Импортировать закладки", + "Import bookmarks…" : "Импортировать закладки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Выберите файл закладок, который экспортирует ваш браузер. Каждая выбранная папка станет группой плиток внизу этой панели.", + "Bookmarks file" : "Файл закладок", + "What to import" : "Что импортировать", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "В этом файле нет закладок.", + "Tiles added: {count}" : "Добавлено плиток: {count}", + "Import" : "Импортировать", + "This file is larger than 5 MB. Export one folder at a time." : "Этот файл больше 5 МБ. Экспортируйте по одной папке.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "В этом файле больше 2000 закладок. Экспортируйте по одной папке.", + "This file could not be read as a bookmarks file." : "Этот файл не удалось прочитать как файл закладок.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Осталось места на этой панели: {count}. Выберите меньше папок.", + "The import failed; nothing was added." : "Импорт не удался; ничего не добавлено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущена {count} закладка: плиткой может стать только веб-адрес", "Пропущено {count} закладки: плиткой может стать только веб-адрес", "Пропущено {count} закладок: плиткой может стать только веб-адрес"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/ru.json b/l10n/ru.json index fd37f3fdd..025166aa3 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Не удалось загрузить ярлыки поиска.", "The search shortcuts could not be saved.": "Не удалось сохранить ярлыки поиска.", "Search {name} for {query}": "Искать в {name}: {query}", - "Type what to search for in {name}": "Введите, что искать в {name}" + "Type what to search for in {name}": "Введите, что искать в {name}", + "Import bookmarks": "Импортировать закладки", + "Import bookmarks…": "Импортировать закладки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Выберите файл закладок, который экспортирует ваш браузер. Каждая выбранная папка станет группой плиток внизу этой панели.", + "Bookmarks file": "Файл закладок", + "What to import": "Что импортировать", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "В этом файле нет закладок.", + "Tiles added: {count}": "Добавлено плиток: {count}", + "Import": "Импортировать", + "This file is larger than 5 MB. Export one folder at a time.": "Этот файл больше 5 МБ. Экспортируйте по одной папке.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "В этом файле больше 2000 закладок. Экспортируйте по одной папке.", + "This file could not be read as a bookmarks file.": "Этот файл не удалось прочитать как файл закладок.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Осталось места на этой панели: {count}. Выберите меньше папок.", + "The import failed; nothing was added.": "Импорт не удался; ничего не добавлено.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Пропущена {count} закладка: плиткой может стать только веб-адрес", + "Пропущено {count} закладки: плиткой может стать только веб-адрес", + "Пропущено {count} закладок: плиткой может стать только веб-адрес" + ] } } diff --git a/l10n/sk.js b/l10n/sk.js index abf4fb4c6..a8791a51d 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Skratky vyhľadávania sa nepodarilo načítať.", "The search shortcuts could not be saved." : "Skratky vyhľadávania sa nepodarilo uložiť.", "Search {name} for {query}" : "Hľadať v {name}: {query}", - "Type what to search for in {name}" : "Napíšte, čo hľadať v {name}" + "Type what to search for in {name}" : "Napíšte, čo hľadať v {name}", + "Import bookmarks" : "Importovať záložky", + "Import bookmarks…" : "Importovať záložky…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Vyberte súbor záložiek, ktorý exportuje váš prehliadač. Každý vybraný priečinok sa stane skupinou dlaždíc v dolnej časti tohto panela.", + "Bookmarks file" : "Súbor záložiek", + "What to import" : "Čo importovať", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Tento súbor neobsahuje žiadne záložky.", + "Tiles added: {count}" : "Pridané dlaždice: {count}", + "Import" : "Importovať", + "This file is larger than 5 MB. Export one folder at a time." : "Tento súbor je väčší ako 5 MB. Exportujte priečinky po jednom.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Tento súbor má viac ako 2 000 záložiek. Exportujte priečinky po jednom.", + "This file could not be read as a bookmarks file." : "Tento súbor sa nepodarilo prečítať ako súbor záložiek.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Zostávajúce miesto na tomto paneli: {count}. Vyberte menej priečinkov.", + "The import failed; nothing was added." : "Import zlyhal; nič nebolo pridané.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočená {count} záložka: dlaždicou môže byť len webová adresa", "Preskočené {count} záložky: dlaždicou môže byť len webová adresa", "Preskočených {count} záložiek: dlaždicou môže byť len webová adresa"] }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/sk.json b/l10n/sk.json index d641da3ee..bba7099b6 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Skratky vyhľadávania sa nepodarilo načítať.", "The search shortcuts could not be saved.": "Skratky vyhľadávania sa nepodarilo uložiť.", "Search {name} for {query}": "Hľadať v {name}: {query}", - "Type what to search for in {name}": "Napíšte, čo hľadať v {name}" + "Type what to search for in {name}": "Napíšte, čo hľadať v {name}", + "Import bookmarks": "Importovať záložky", + "Import bookmarks…": "Importovať záložky…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Vyberte súbor záložiek, ktorý exportuje váš prehliadač. Každý vybraný priečinok sa stane skupinou dlaždíc v dolnej časti tohto panela.", + "Bookmarks file": "Súbor záložiek", + "What to import": "Čo importovať", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Tento súbor neobsahuje žiadne záložky.", + "Tiles added: {count}": "Pridané dlaždice: {count}", + "Import": "Importovať", + "This file is larger than 5 MB. Export one folder at a time.": "Tento súbor je väčší ako 5 MB. Exportujte priečinky po jednom.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Tento súbor má viac ako 2 000 záložiek. Exportujte priečinky po jednom.", + "This file could not be read as a bookmarks file.": "Tento súbor sa nepodarilo prečítať ako súbor záložiek.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Zostávajúce miesto na tomto paneli: {count}. Vyberte menej priečinkov.", + "The import failed; nothing was added.": "Import zlyhal; nič nebolo pridané.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Preskočená {count} záložka: dlaždicou môže byť len webová adresa", + "Preskočené {count} záložky: dlaždicou môže byť len webová adresa", + "Preskočených {count} záložiek: dlaždicou môže byť len webová adresa" + ] } } diff --git a/l10n/sl.js b/l10n/sl.js index 7b5c86ecd..c7d57f454 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Bližnjic iskanja ni bilo mogoče naložiti.", "The search shortcuts could not be saved." : "Bližnjic iskanja ni bilo mogoče shraniti.", "Search {name} for {query}" : "Išči v {name}: {query}", - "Type what to search for in {name}" : "Vnesite, kaj iskati v {name}" + "Type what to search for in {name}" : "Vnesite, kaj iskati v {name}", + "Import bookmarks" : "Uvozi zaznamke", + "Import bookmarks…" : "Uvozi zaznamke…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Izberite datoteko z zaznamki, ki jo izvozi vaš brskalnik. Vsaka izbrana mapa postane skupina ploščic na dnu te nadzorne plošče.", + "Bookmarks file" : "Datoteka z zaznamki", + "What to import" : "Kaj uvoziti", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ta datoteka ne vsebuje zaznamkov.", + "Tiles added: {count}" : "Dodane ploščice: {count}", + "Import" : "Uvozi", + "This file is larger than 5 MB. Export one folder at a time." : "Ta datoteka je večja od 5 MB. Izvažajte po eno mapo.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ta datoteka ima več kot 2.000 zaznamkov. Izvažajte po eno mapo.", + "This file could not be read as a bookmarks file." : "Te datoteke ni bilo mogoče prebrati kot datoteke z zaznamki.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Preostali prostor na tej nadzorni plošči: {count}. Izberite manj map.", + "The import failed; nothing was added." : "Uvoz ni uspel; nič ni bilo dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočen {count} zaznamek: ploščica je lahko le spletni naslov", "Preskočena {count} zaznamka: ploščica je lahko le spletni naslov", "Preskočeni {count} zaznamki: ploščica je lahko le spletni naslov", "Preskočenih {count} zaznamkov: ploščica je lahko le spletni naslov"] }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" ); diff --git a/l10n/sl.json b/l10n/sl.json index 939bb84b4..87d99fe55 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -1313,6 +1313,26 @@ "The search shortcuts could not be loaded.": "Bližnjic iskanja ni bilo mogoče naložiti.", "The search shortcuts could not be saved.": "Bližnjic iskanja ni bilo mogoče shraniti.", "Search {name} for {query}": "Išči v {name}: {query}", - "Type what to search for in {name}": "Vnesite, kaj iskati v {name}" + "Type what to search for in {name}": "Vnesite, kaj iskati v {name}", + "Import bookmarks": "Uvozi zaznamke", + "Import bookmarks…": "Uvozi zaznamke…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Izberite datoteko z zaznamki, ki jo izvozi vaš brskalnik. Vsaka izbrana mapa postane skupina ploščic na dnu te nadzorne plošče.", + "Bookmarks file": "Datoteka z zaznamki", + "What to import": "Kaj uvoziti", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ta datoteka ne vsebuje zaznamkov.", + "Tiles added: {count}": "Dodane ploščice: {count}", + "Import": "Uvozi", + "This file is larger than 5 MB. Export one folder at a time.": "Ta datoteka je večja od 5 MB. Izvažajte po eno mapo.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ta datoteka ima več kot 2.000 zaznamkov. Izvažajte po eno mapo.", + "This file could not be read as a bookmarks file.": "Te datoteke ni bilo mogoče prebrati kot datoteke z zaznamki.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Preostali prostor na tej nadzorni plošči: {count}. Izberite manj map.", + "The import failed; nothing was added.": "Uvoz ni uspel; nič ni bilo dodano.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Preskočen {count} zaznamek: ploščica je lahko le spletni naslov", + "Preskočena {count} zaznamka: ploščica je lahko le spletni naslov", + "Preskočeni {count} zaznamki: ploščica je lahko le spletni naslov", + "Preskočenih {count} zaznamkov: ploščica je lahko le spletni naslov" + ] } } diff --git a/l10n/sq.js b/l10n/sq.js index 0bd833a22..6160dad4d 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Shkurtoret e kërkimit nuk u ngarkuan.", "The search shortcuts could not be saved." : "Shkurtoret e kërkimit nuk u ruajtën.", "Search {name} for {query}" : "Kërko në {name} për {query}", - "Type what to search for in {name}" : "Shkruaj çfarë të kërkosh në {name}" + "Type what to search for in {name}" : "Shkruaj çfarë të kërkosh në {name}", + "Import bookmarks" : "Importo faqerojtës", + "Import bookmarks…" : "Importo faqerojtës…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Zgjidh skedarin e faqerojtësve që eksporton shfletuesi yt. Çdo dosje që zgjedh bëhet një grup pllakash në fund të këtij paneli.", + "Bookmarks file" : "Skedar faqerojtësish", + "What to import" : "Çfarë të importohet", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ky skedar nuk ka faqerojtës.", + "Tiles added: {count}" : "Pllaka të shtuara: {count}", + "Import" : "Importo", + "This file is larger than 5 MB. Export one folder at a time." : "Ky skedar është më i madh se 5 MB. Eksporto një dosje në një kohë.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ky skedar ka më shumë se 2.000 faqerojtës. Eksporto një dosje në një kohë.", + "This file could not be read as a bookmarks file." : "Ky skedar nuk u lexua dot si skedar faqerojtësish.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Hapësirë e mbetur në këtë panel: {count}. Zgjidh më pak dosje.", + "The import failed; nothing was added." : "Importimi dështoi; nuk u shtua asgjë.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["U anashkalua {count} faqerojtës: vetëm adresat web mund të bëhen pllaka", "U anashkaluan {count} faqerojtës: vetëm adresat web mund të bëhen pllaka"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sq.json b/l10n/sq.json index 5ac4b139f..e8e26ffc0 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Shkurtoret e kërkimit nuk u ngarkuan.", "The search shortcuts could not be saved.": "Shkurtoret e kërkimit nuk u ruajtën.", "Search {name} for {query}": "Kërko në {name} për {query}", - "Type what to search for in {name}": "Shkruaj çfarë të kërkosh në {name}" + "Type what to search for in {name}": "Shkruaj çfarë të kërkosh në {name}", + "Import bookmarks": "Importo faqerojtës", + "Import bookmarks…": "Importo faqerojtës…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Zgjidh skedarin e faqerojtësve që eksporton shfletuesi yt. Çdo dosje që zgjedh bëhet një grup pllakash në fund të këtij paneli.", + "Bookmarks file": "Skedar faqerojtësish", + "What to import": "Çfarë të importohet", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ky skedar nuk ka faqerojtës.", + "Tiles added: {count}": "Pllaka të shtuara: {count}", + "Import": "Importo", + "This file is larger than 5 MB. Export one folder at a time.": "Ky skedar është më i madh se 5 MB. Eksporto një dosje në një kohë.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ky skedar ka më shumë se 2.000 faqerojtës. Eksporto një dosje në një kohë.", + "This file could not be read as a bookmarks file.": "Ky skedar nuk u lexua dot si skedar faqerojtësish.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Hapësirë e mbetur në këtë panel: {count}. Zgjidh më pak dosje.", + "The import failed; nothing was added.": "Importimi dështoi; nuk u shtua asgjë.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "U anashkalua {count} faqerojtës: vetëm adresat web mund të bëhen pllaka", + "U anashkaluan {count} faqerojtës: vetëm adresat web mund të bëhen pllaka" + ] } } diff --git a/l10n/sr.js b/l10n/sr.js index 7a730dee2..ac7bf0a0d 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -1335,6 +1335,21 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Пречице за претрагу нису могле да се учитају.", "The search shortcuts could not be saved." : "Пречице за претрагу нису могле да се сачувају.", "Search {name} for {query}" : "Претражи {name} за {query}", - "Type what to search for in {name}" : "Унесите шта тражите у {name}" + "Type what to search for in {name}" : "Унесите шта тражите у {name}", + "Import bookmarks" : "Увези обележиваче", + "Import bookmarks…" : "Увези обележиваче…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Изаберите датотеку обележивача коју извози ваш прегледач. Свака изабрана фасцикла постаје група плочица на дну ове контролне табле.", + "Bookmarks file" : "Датотека обележивача", + "What to import" : "Шта увести", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Ова датотека нема обележиваче.", + "Tiles added: {count}" : "Додато плочица: {count}", + "Import" : "Увези", + "This file is larger than 5 MB. Export one folder at a time." : "Ова датотека је већа од 5 MB. Извозите једну по једну фасциклу.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Ова датотека има више од 2.000 обележивача. Извозите једну по једну фасциклу.", + "This file could not be read as a bookmarks file." : "Ова датотека не може да се прочита као датотека обележивача.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Преостало места на овој контролној табли: {count}. Изаберите мање фасцикли.", + "The import failed; nothing was added." : "Увоз није успео; ништа није додато.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескочен {count} обележивач: само веб адресе могу постати плочице", "Прескочена {count} обележивача: само веб адресе могу постати плочице", "Прескочено {count} обележивача: само веб адресе могу постати плочице"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/sr.json b/l10n/sr.json index b2c919ccb..2fe773f5e 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Пречице за претрагу нису могле да се учитају.", "The search shortcuts could not be saved.": "Пречице за претрагу нису могле да се сачувају.", "Search {name} for {query}": "Претражи {name} за {query}", - "Type what to search for in {name}": "Унесите шта тражите у {name}" + "Type what to search for in {name}": "Унесите шта тражите у {name}", + "Import bookmarks": "Увези обележиваче", + "Import bookmarks…": "Увези обележиваче…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Изаберите датотеку обележивача коју извози ваш прегледач. Свака изабрана фасцикла постаје група плочица на дну ове контролне табле.", + "Bookmarks file": "Датотека обележивача", + "What to import": "Шта увести", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Ова датотека нема обележиваче.", + "Tiles added: {count}": "Додато плочица: {count}", + "Import": "Увези", + "This file is larger than 5 MB. Export one folder at a time.": "Ова датотека је већа од 5 MB. Извозите једну по једну фасциклу.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Ова датотека има више од 2.000 обележивача. Извозите једну по једну фасциклу.", + "This file could not be read as a bookmarks file.": "Ова датотека не може да се прочита као датотека обележивача.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Преостало места на овој контролној табли: {count}. Изаберите мање фасцикли.", + "The import failed; nothing was added.": "Увоз није успео; ништа није додато.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Прескочен {count} обележивач: само веб адресе могу постати плочице", + "Прескочена {count} обележивача: само веб адресе могу постати плочице", + "Прескочено {count} обележивача: само веб адресе могу постати плочице" + ] } } diff --git a/l10n/sv.js b/l10n/sv.js index c6779b6f7..92a458d91 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Sökgenvägarna kunde inte läsas in.", "The search shortcuts could not be saved." : "Sökgenvägarna kunde inte sparas.", "Search {name} for {query}" : "Sök i {name} efter {query}", - "Type what to search for in {name}" : "Skriv vad du vill söka efter i {name}" + "Type what to search for in {name}" : "Skriv vad du vill söka efter i {name}", + "Import bookmarks" : "Importera bokmärken", + "Import bookmarks…" : "Importera bokmärken…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Välj bokmärkesfilen som din webbläsare exporterar. Varje mapp du väljer blir en grupp paneler längst ner på den här instrumentpanelen.", + "Bookmarks file" : "Bokmärkesfil", + "What to import" : "Vad som ska importeras", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Den här filen innehåller inga bokmärken.", + "Tiles added: {count}" : "Paneler tillagda: {count}", + "Import" : "Importera", + "This file is larger than 5 MB. Export one folder at a time." : "Filen är större än 5 MB. Exportera en mapp i taget.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Filen har fler än 2 000 bokmärken. Exportera en mapp i taget.", + "This file could not be read as a bookmarks file." : "Filen kunde inte läsas som en bokmärkesfil.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Plats kvar på den här instrumentpanelen: {count}. Välj färre mappar.", + "The import failed; nothing was added." : "Importen misslyckades; inget lades till.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmärke hoppades över: bara webbadresser kan bli paneler", "{count} bokmärken hoppades över: bara webbadresser kan bli paneler"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sv.json b/l10n/sv.json index 84408ac33..90948ab96 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Sökgenvägarna kunde inte läsas in.", "The search shortcuts could not be saved.": "Sökgenvägarna kunde inte sparas.", "Search {name} for {query}": "Sök i {name} efter {query}", - "Type what to search for in {name}": "Skriv vad du vill söka efter i {name}" + "Type what to search for in {name}": "Skriv vad du vill söka efter i {name}", + "Import bookmarks": "Importera bokmärken", + "Import bookmarks…": "Importera bokmärken…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Välj bokmärkesfilen som din webbläsare exporterar. Varje mapp du väljer blir en grupp paneler längst ner på den här instrumentpanelen.", + "Bookmarks file": "Bokmärkesfil", + "What to import": "Vad som ska importeras", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Den här filen innehåller inga bokmärken.", + "Tiles added: {count}": "Paneler tillagda: {count}", + "Import": "Importera", + "This file is larger than 5 MB. Export one folder at a time.": "Filen är större än 5 MB. Exportera en mapp i taget.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Filen har fler än 2 000 bokmärken. Exportera en mapp i taget.", + "This file could not be read as a bookmarks file.": "Filen kunde inte läsas som en bokmärkesfil.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Plats kvar på den här instrumentpanelen: {count}. Välj färre mappar.", + "The import failed; nothing was added.": "Importen misslyckades; inget lades till.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} bokmärke hoppades över: bara webbadresser kan bli paneler", + "{count} bokmärken hoppades över: bara webbadresser kan bli paneler" + ] } } diff --git a/l10n/tr.js b/l10n/tr.js index a6c2bc1c4..8697db854 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Arama kısayolları yüklenemedi.", "The search shortcuts could not be saved." : "Arama kısayolları kaydedilemedi.", "Search {name} for {query}" : "{name} içinde {query} ara", - "Type what to search for in {name}" : "{name} içinde ne aranacağını yaz" + "Type what to search for in {name}" : "{name} içinde ne aranacağını yaz", + "Import bookmarks" : "Yer imlerini içe aktar", + "Import bookmarks…" : "Yer imlerini içe aktar…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Tarayıcının dışa aktardığı yer imi dosyasını seç. Seçtiğin her klasör bu panonun altında bir kutucuk grubu olur.", + "Bookmarks file" : "Yer imi dosyası", + "What to import" : "Neler içe aktarılsın", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "Bu dosyada yer imi yok.", + "Tiles added: {count}" : "Eklenen kutucuklar: {count}", + "Import" : "İçe aktar", + "This file is larger than 5 MB. Export one folder at a time." : "Bu dosya 5 MB'tan büyük. Her seferinde bir klasör dışa aktar.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "Bu dosyada 2.000'den fazla yer imi var. Her seferinde bir klasör dışa aktar.", + "This file could not be read as a bookmarks file." : "Bu dosya bir yer imi dosyası olarak okunamadı.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Bu panoda kalan yer: {count}. Daha az klasör seç.", + "The import failed; nothing was added." : "İçe aktarma başarısız oldu; hiçbir şey eklenmedi.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir", "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir"] }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/tr.json b/l10n/tr.json index b402ba896..2181ac1fc 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -1311,6 +1311,24 @@ "The search shortcuts could not be loaded.": "Arama kısayolları yüklenemedi.", "The search shortcuts could not be saved.": "Arama kısayolları kaydedilemedi.", "Search {name} for {query}": "{name} içinde {query} ara", - "Type what to search for in {name}": "{name} içinde ne aranacağını yaz" + "Type what to search for in {name}": "{name} içinde ne aranacağını yaz", + "Import bookmarks": "Yer imlerini içe aktar", + "Import bookmarks…": "Yer imlerini içe aktar…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Tarayıcının dışa aktardığı yer imi dosyasını seç. Seçtiğin her klasör bu panonun altında bir kutucuk grubu olur.", + "Bookmarks file": "Yer imi dosyası", + "What to import": "Neler içe aktarılsın", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "Bu dosyada yer imi yok.", + "Tiles added: {count}": "Eklenen kutucuklar: {count}", + "Import": "İçe aktar", + "This file is larger than 5 MB. Export one folder at a time.": "Bu dosya 5 MB'tan büyük. Her seferinde bir klasör dışa aktar.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "Bu dosyada 2.000'den fazla yer imi var. Her seferinde bir klasör dışa aktar.", + "This file could not be read as a bookmarks file.": "Bu dosya bir yer imi dosyası olarak okunamadı.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Bu panoda kalan yer: {count}. Daha az klasör seç.", + "The import failed; nothing was added.": "İçe aktarma başarısız oldu; hiçbir şey eklenmedi.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir", + "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir" + ] } } diff --git a/l10n/uk.js b/l10n/uk.js index ffc923a4a..821218bb4 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -1344,7 +1344,22 @@ OC.L10N.register( "The search shortcuts could not be loaded." : "Не вдалося завантажити ярлики пошуку.", "The search shortcuts could not be saved." : "Не вдалося зберегти ярлики пошуку.", "Search {name} for {query}" : "Шукати в {name}: {query}", - "Type what to search for in {name}" : "Введіть, що шукати в {name}" + "Type what to search for in {name}" : "Введіть, що шукати в {name}", + "Import bookmarks" : "Імпортувати закладки", + "Import bookmarks…" : "Імпортувати закладки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard." : "Виберіть файл закладок, який експортує ваш браузер. Кожна вибрана тека стане групою плиток унизу цієї панелі.", + "Bookmarks file" : "Файл закладок", + "What to import" : "Що імпортувати", + "{name} ({count})" : "{name} ({count})", + "This file holds no bookmarks." : "У цьому файлі немає закладок.", + "Tiles added: {count}" : "Додано плиток: {count}", + "Import" : "Імпортувати", + "This file is larger than 5 MB. Export one folder at a time." : "Цей файл більший за 5 МБ. Експортуйте по одній теці.", + "This file has more than 2,000 bookmarks. Export one folder at a time." : "У цьому файлі понад 2000 закладок. Експортуйте по одній теці.", + "This file could not be read as a bookmarks file." : "Цей файл не вдалося прочитати як файл закладок.", + "Room left on this dashboard: {count}. Pick fewer folders." : "Залишилося місця на цій панелі: {count}. Виберіть менше тек.", + "The import failed; nothing was added." : "Імпорт не вдався; нічого не додано.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущено {count} закладку: плиткою може стати лише веб-адреса", "Пропущено {count} закладки: плиткою може стати лише веб-адреса", "Пропущено {count} закладок: плиткою може стати лише веб-адреса"] }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/uk.json b/l10n/uk.json index ad873f69a..d18eee5a7 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -1312,6 +1312,25 @@ "The search shortcuts could not be loaded.": "Не вдалося завантажити ярлики пошуку.", "The search shortcuts could not be saved.": "Не вдалося зберегти ярлики пошуку.", "Search {name} for {query}": "Шукати в {name}: {query}", - "Type what to search for in {name}": "Введіть, що шукати в {name}" + "Type what to search for in {name}": "Введіть, що шукати в {name}", + "Import bookmarks": "Імпортувати закладки", + "Import bookmarks…": "Імпортувати закладки…", + "Choose the bookmarks file your browser exports. Each folder you pick becomes a group of tiles at the bottom of this dashboard.": "Виберіть файл закладок, який експортує ваш браузер. Кожна вибрана тека стане групою плиток унизу цієї панелі.", + "Bookmarks file": "Файл закладок", + "What to import": "Що імпортувати", + "{name} ({count})": "{name} ({count})", + "This file holds no bookmarks.": "У цьому файлі немає закладок.", + "Tiles added: {count}": "Додано плиток: {count}", + "Import": "Імпортувати", + "This file is larger than 5 MB. Export one folder at a time.": "Цей файл більший за 5 МБ. Експортуйте по одній теці.", + "This file has more than 2,000 bookmarks. Export one folder at a time.": "У цьому файлі понад 2000 закладок. Експортуйте по одній теці.", + "This file could not be read as a bookmarks file.": "Цей файл не вдалося прочитати як файл закладок.", + "Room left on this dashboard: {count}. Pick fewer folders.": "Залишилося місця на цій панелі: {count}. Виберіть менше тек.", + "The import failed; nothing was added.": "Імпорт не вдався; нічого не додано.", + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ + "Пропущено {count} закладку: плиткою може стати лише веб-адреса", + "Пропущено {count} закладки: плиткою може стати лише веб-адреса", + "Пропущено {count} закладок: плиткою може стати лише веб-адреса" + ] } } diff --git a/lib/Controller/BookmarkImportController.php b/lib/Controller/BookmarkImportController.php new file mode 100644 index 000000000..dd09ead0d --- /dev/null +++ b/lib/Controller/BookmarkImportController.php @@ -0,0 +1,120 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT:auto + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Controller; + +use InvalidArgumentException; +use OCA\LaunchPad\AppInfo\Application; +use OCA\LaunchPad\Exception\QuotaExceededException; +use OCA\LaunchPad\Service\ActionAuthService; +use OCA\LaunchPad\Service\BookmarkImportService; +use OCA\LaunchPad\Service\PermissionService; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\IRequest; +use OCP\IUserSession; +use Psr\Log\LoggerInterface; +use Throwable; + +/** + * Bookmark import endpoint. + * + * @spec openspec/specs/tiles/spec.md + */ +class BookmarkImportController extends Controller { + /** + * Constructor. + * + * @param IRequest $request HTTP request. + * @param BookmarkImportService $importer The import. + * @param PermissionService $permissionService Add rights on the dashboard. + * @param ActionAuthService $actionAuth ADR-023 action authorization. + * @param IUserSession $userSession Current user. + * @param LoggerInterface $logger Logger. + * + * @spec openspec/specs/tiles/spec.md + */ + public function __construct( + IRequest $request, + private readonly BookmarkImportService $importer, + private readonly PermissionService $permissionService, + private readonly ActionAuthService $actionAuth, + private readonly IUserSession $userSession, + private readonly LoggerInterface $logger, + ) { + parent::__construct( + appName: Application::APP_ID, + request: $request + ); + }//end __construct() + + /** + * Import the chosen bookmarks. The caller needs add rights on the + * dashboard (`add_only` or `full`), like adding one tile. + * + * @param int $dashboardId The dashboard. + * @param array|null $folders `[{name, bookmarks: [{title, url}]}]`. + * @param array|null $bookmarks Loose `[{title, url}]`. + * + * @return JSONResponse 201 with `{placements, containers, tiles, skipped}`, 400, 401, 403, 409 or 500. + * + * @spec openspec/specs/tiles/spec.md + */ + #[NoAdminRequired] + public function import(int $dashboardId, ?array $folders = null, ?array $bookmarks = null): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse(data: ['error' => 'Not authenticated'], statusCode: Http::STATUS_UNAUTHORIZED); + } + + try { + $this->actionAuth->requireAction($user, 'widget.import-bookmarks'); + } catch (OCSForbiddenException) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + if ($this->permissionService->canAddWidget(userId: $user->getUID(), dashboardId: $dashboardId) === false) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $result = $this->importer->import( + dashboardId: $dashboardId, + folders: ($folders ?? []), + bookmarks: ($bookmarks ?? []) + ); + } catch (QuotaExceededException $e) { + return ResponseHelper::quotaExceeded(exception: $e); + } catch (InvalidArgumentException $e) { + return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: Http::STATUS_BAD_REQUEST); + } catch (Throwable $e) { + $this->logger->error(message: 'Bookmark import failed: ' . $e->getMessage(), context: ['exception' => $e]); + return new JSONResponse(data: ['error' => 'The import failed; nothing was added.'], statusCode: Http::STATUS_INTERNAL_SERVER_ERROR); + } + + return new JSONResponse(data: $result, statusCode: Http::STATUS_CREATED); + }//end import() +}//end class diff --git a/lib/Service/BookmarkImportService.php b/lib/Service/BookmarkImportService.php new file mode 100644 index 000000000..8e6028f1d --- /dev/null +++ b/lib/Service/BookmarkImportService.php @@ -0,0 +1,340 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\WidgetPlacementMapper; +use OCP\IDBConnection; +use Throwable; + +/** + * Bookmark import. + * + * @spec openspec/specs/tiles/spec.md + */ +class BookmarkImportService { + /** + * Most bookmarks one import may carry (REQ-BMI-004, also checked here). + * + * @var int + */ + public const MAX_BOOKMARKS = 2000; + + /** + * Outer grid columns the loose tiles fill. + * + * @var int + */ + private const GRID_COLUMNS = 12; + + /** + * Tile size in grid cells, outer and inner. + * + * @var int + */ + private const TILE_SIZE = 2; + + /** + * Container width in outer columns; its inner grid has 4 columns. + * + * @var int + */ + private const CONTAINER_WIDTH = 4; + + /** + * Constructor. + * + * @param PlacementService $placements Creates placements (with the per-placement quota check). + * @param WidgetPlacementMapper $placementMapper Reads the current layout for the bottom row. + * @param QuotaService $quota Checks the whole import against the widget quota. + * @param IDBConnection $db Transaction. + * + * @spec openspec/specs/tiles/spec.md + */ + public function __construct( + private readonly PlacementService $placements, + private readonly WidgetPlacementMapper $placementMapper, + private readonly QuotaService $quota, + private readonly IDBConnection $db, + ) { + }//end __construct() + + /** + * Import the chosen folders and loose bookmarks. + * + * @param int $dashboardId The dashboard (the caller checked add rights). + * @param array $folders `[{name, bookmarks: [{title, url}]}]`. + * @param array $bookmarks Loose `[{title, url}]`. + * + * @return array{placements: array, containers: int, tiles: int, skipped: array} + * + * @throws InvalidArgumentException When the import carries too many bookmarks. + * @throws \OCA\LaunchPad\Exception\QuotaExceededException When the dashboard has no room. + * + * @spec openspec/specs/tiles/spec.md + */ + public function import(int $dashboardId, array $folders, array $bookmarks): array { + $skipped = []; + $cleanFolders = []; + $total = 0; + foreach ($folders as $folder) { + $items = $this->keepWebAddresses(raw: (array)($folder['bookmarks'] ?? []), skipped: $skipped, total: $total); + if ($items !== []) { + $cleanFolders[] = ['name' => $this->folderName(raw: $folder['name'] ?? ''), 'bookmarks' => $items]; + } + } + + $loose = $this->keepWebAddresses(raw: $bookmarks, skipped: $skipped, total: $total); + if ($total > self::MAX_BOOKMARKS) { + throw new InvalidArgumentException(message: 'At most ' . self::MAX_BOOKMARKS . ' bookmarks per import'); + } + + // REQ-BMI-003: one check for the whole import, before anything is written. + $this->quota->assertRoomFor(dashboardId: $dashboardId, count: count(value: $cleanFolders) + count(value: $loose)); + + $created = []; + $this->db->beginTransaction(); + try { + $created = $this->createAll(dashboardId: $dashboardId, folders: $cleanFolders, loose: $loose); + $this->db->commit(); + } catch (Throwable $e) { + $this->db->rollBack(); + throw $e; + } + + $tiles = count(value: $loose); + foreach ($cleanFolders as $folder) { + $tiles += count(value: $folder['bookmarks']); + } + + return [ + 'placements' => array_map(callback: static fn ($placement) => $placement->jsonSerialize(), array: $created), + 'containers' => count(value: $cleanFolders), + 'tiles' => $tiles, + 'skipped' => $skipped, + ]; + }//end import() + + /** + * Create the containers, then the loose tiles, below the lowest widget. + * + * @param int $dashboardId The dashboard. + * @param array $folders Clean folders. + * @param array $loose Clean loose bookmarks. + * + * @return array The created placements. + */ + private function createAll(int $dashboardId, array $folders, array $loose): array { + $created = []; + $row = $this->bottomRow(dashboardId: $dashboardId); + $column = 0; + $rowHeight = 0; + foreach ($folders as $folder) { + $height = $this->containerHeight(count: count(value: $folder['bookmarks'])); + if ($column + self::CONTAINER_WIDTH > self::GRID_COLUMNS) { + $column = 0; + $row += $rowHeight; + $rowHeight = 0; + } + + $created[] = $this->placements->addWidget( + dashboardId: $dashboardId, + widgetId: 'container', + gridX: $column, + gridY: $row, + gridWidth: self::CONTAINER_WIDTH, + gridHeight: $height, + content: [ + 'title' => $folder['name'], + 'backgroundColor' => 'transparent', + 'padding' => 'medium', + 'placements' => $this->children(bookmarks: $folder['bookmarks']), + ] + ); + $column += self::CONTAINER_WIDTH; + $rowHeight = max($rowHeight, $height); + }//end foreach + + if ($loose !== []) { + $row += $rowHeight; + $column = 0; + } + + foreach ($loose as $bookmark) { + if ($column + self::TILE_SIZE > self::GRID_COLUMNS) { + $column = 0; + $row += self::TILE_SIZE; + } + + $created[] = $this->placements->addTileFromArray( + dashboardId: $dashboardId, + tileData: [ + 'title' => $bookmark['title'], + 'icon' => 'icon-link', + 'iconType' => 'class', + 'linkType' => 'url', + 'linkVal' => $bookmark['url'], + 'gridX' => $column, + 'gridY' => $row, + 'gridWidth' => self::TILE_SIZE, + 'gridHeight' => self::TILE_SIZE, + ] + ); + $column += self::TILE_SIZE; + }//end foreach + + return $created; + }//end createAll() + + /** + * Keep bookmarks with an http or https address; list the rest as skipped. + * + * @param array $raw Raw `[{title, url}]`. + * @param array $skipped Skipped list, appended to. + * @param int $total Running count of every bookmark seen. + * + * @return array + */ + private function keepWebAddresses(array $raw, array &$skipped, int &$total): array { + $kept = []; + foreach ($raw as $bookmark) { + $total++; + $url = trim(string: (string)($bookmark['url'] ?? '')); + $title = mb_substr(string: trim(string: (string)($bookmark['title'] ?? '')), start: 0, length: 255); + if (self::isWebAddress(url: $url) === false) { + $skipped[] = ['title' => $title, 'url' => mb_substr(string: $url, start: 0, length: 255), 'reason' => 'not-a-web-address']; + continue; + } + + if ($title === '') { + $title = (string)parse_url(url: $url, component: PHP_URL_HOST); + } + + $kept[] = ['title' => $title, 'url' => $url]; + } + + return $kept; + }//end keepWebAddresses() + + /** + * Whether an address is an http or https web address with a host + * (REQ-BMI-002). A tile only opens the address in the person's own + * browser, so the server never fetches it and intranet hosts are fine. + * + * @param string $url The address. + * + * @return bool + * + * @spec openspec/specs/tiles/spec.md + */ + public static function isWebAddress(string $url): bool { + if ($url === '' || strlen(string: $url) > 2048) { + return false; + } + + $scheme = strtolower(string: (string)parse_url(url: $url, component: PHP_URL_SCHEME)); + if ($scheme !== 'http' && $scheme !== 'https') { + return false; + } + + return (string)parse_url(url: $url, component: PHP_URL_HOST) !== ''; + }//end isWebAddress() + + /** + * A folder name, or "Bookmarks" when empty. + * + * @param mixed $raw The name. + * + * @return string + */ + private function folderName(mixed $raw): string { + $name = mb_substr(string: trim(string: (string)$raw), start: 0, length: 255); + if ($name === '') { + return 'Bookmarks'; + } + + return $name; + }//end folderName() + + /** + * Link tiles for a container's inner grid: 2 by 2, two per row. + * + * @param array $bookmarks Clean bookmarks. + * + * @return array Child placements. + */ + private function children(array $bookmarks): array { + $children = []; + foreach (array_values(array: $bookmarks) as $index => $bookmark) { + $children[] = [ + 'uuid' => bin2hex(string: random_bytes(length: 16)), + 'type' => 'link', + 'content' => [ + 'label' => $bookmark['title'], + 'url' => $bookmark['url'], + 'actionType' => 'external', + 'icon' => '', + 'displayMode' => 'button', + ], + 'gridX' => ($index % 2) * self::TILE_SIZE, + 'gridY' => intdiv(num1: $index, num2: 2) * self::TILE_SIZE, + 'gridWidth' => self::TILE_SIZE, + 'gridHeight' => self::TILE_SIZE, + ]; + } + + return $children; + }//end children() + + /** + * Outer height of a container holding `$count` tiles: inner rows of + * 2 cells of 40 px, in outer cells of 60 px, plus one for the title. + * + * @param int $count Number of tiles. + * + * @return int Outer grid cells. + */ + private function containerHeight(int $count): int { + $innerRows = (int)ceil(num: $count / 2); + return max(2, (int)ceil(num: ($innerRows * self::TILE_SIZE * 40) / 60) + 1); + }//end containerHeight() + + /** + * The first free row below every widget on the dashboard. + * + * @param int $dashboardId The dashboard. + * + * @return int The row. + */ + private function bottomRow(int $dashboardId): int { + $bottom = 0; + foreach ($this->placementMapper->findByDashboardId(dashboardId: $dashboardId) as $placement) { + $bottom = max($bottom, (int)$placement->getGridY() + (int)$placement->getGridHeight()); + } + + return $bottom; + }//end bottomRow() +}//end class diff --git a/lib/Service/QuotaService.php b/lib/Service/QuotaService.php index 44ef587a8..ff64b37bc 100644 --- a/lib/Service/QuotaService.php +++ b/lib/Service/QuotaService.php @@ -207,6 +207,40 @@ public function assertCanAddPlacement(int $dashboardId): void { } }//end assertCanAddPlacement() + /** + * Assert the dashboard has room for `$count` more placements, checked + * once for a batch such as a bookmark import (launcher-bookmark-import + * REQ-BMI-003). Bypassed during admin provisioning like the single check. + * + * @param int $dashboardId The dashboard. + * @param int $count Placements the batch would add. + * + * @return void + * + * @throws QuotaExceededException When the batch would pass the widget limit. + * + * @spec openspec/specs/tiles/spec.md + */ + public function assertRoomFor(int $dashboardId, int $count): void { + if ($count <= 0 || $this->provisioningDepth > 0) { + return; + } + + $limit = $this->maxWidgetsPerDashboard(); + if ($limit === 0) { + return; + } + + $current = $this->placementMapper->countByDashboardId(dashboardId: $dashboardId); + if ($current + $count > $limit) { + throw new QuotaExceededException( + quota: QuotaExceededException::QUOTA_WIDGETS, + limit: $limit, + current: $current + ); + } + }//end assertRoomFor() + /** * Build the additive quota-status envelope for the dashboards list * response (REQ-QUOTA-006). diff --git a/lib/actions.seed.json b/lib/actions.seed.json index 5f717c98f..dc9b9436d 100644 --- a/lib/actions.seed.json +++ b/lib/actions.seed.json @@ -81,6 +81,7 @@ "widget.get-items": ["admin", "@all"], "widget.add-widget": ["admin", "@all"], "widget.add-tile": ["admin", "@all"], + "widget.import-bookmarks": ["admin", "@all"], "widget.update-placement": ["admin", "@all"], "widget.remove-placement": ["admin", "@all"], "widget.news-items": ["admin", "@all"], diff --git a/src/components/Workspace/DashboardRowActions.vue b/src/components/Workspace/DashboardRowActions.vue index f9c8609a7..7d5176cb0 100644 --- a/src/components/Workspace/DashboardRowActions.vue +++ b/src/components/Workspace/DashboardRowActions.vue @@ -77,6 +77,16 @@ {{ t('launchpad', 'Add custom widget…') }} + + + {{ t('launchpad', 'Import bookmarks…') }} + import { t } from '@nextcloud/l10n' import { NcActionButton, NcActions } from '@nextcloud/vue' +import BookmarkMultipleOutline from 'vue-material-design-icons/BookmarkMultipleOutline.vue' import CalendarClock from 'vue-material-design-icons/CalendarClock.vue' import Cog from 'vue-material-design-icons/Cog.vue' import ContentSave from 'vue-material-design-icons/ContentSave.vue' @@ -176,6 +187,7 @@ export default { name: 'DashboardRowActions', components: { + BookmarkMultipleOutline, NcActions, NcActionButton, Cog, @@ -273,6 +285,16 @@ export default { default: false, }, + /* + * launcher-bookmark-import: offer "Import bookmarks…". Only the + * active-dashboard menu sets it; with `canEdit` it hides the entry + * on view-only dashboards (REQ-BMI-001). + */ + canImportBookmarks: { + type: Boolean, + default: false, + }, + /* * NcActions toggle button style. Defaults to the subtle * `tertiary-no-background` used at the edge of each sidebar row; @@ -306,6 +328,7 @@ export default { 'schedule', 'versionHistory', 'menuOpen', + 'importBookmarks', ], computed: { diff --git a/src/dialogs/BookmarkImportDialog.vue b/src/dialogs/BookmarkImportDialog.vue new file mode 100644 index 000000000..406cc5f1d --- /dev/null +++ b/src/dialogs/BookmarkImportDialog.vue @@ -0,0 +1,336 @@ + + + + + + + diff --git a/src/dialogs/__tests__/BookmarkImportDialog.spec.js b/src/dialogs/__tests__/BookmarkImportDialog.spec.js new file mode 100644 index 000000000..5ed45ad39 --- /dev/null +++ b/src/dialogs/__tests__/BookmarkImportDialog.spec.js @@ -0,0 +1,216 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for `BookmarkImportDialog.vue` and the "Import + * bookmarks…" menu entry (launcher-bookmark-import, REQ-BMI-001..004): the + * file is read in the browser, only the chosen items are sent, skipped + * bookmarks and a full dashboard are explained, an oversized file never + * reaches the server, and a view-only dashboard offers no import. + */ + +import axios from '@nextcloud/axios' +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import DashboardRowActions from '../../components/Workspace/DashboardRowActions.vue' +import BookmarkImportDialog from '../BookmarkImportDialog.vue' + +vi.mock('@nextcloud/axios', () => ({ default: { post: vi.fn() } })) +vi.mock('@nextcloud/router', async (importOriginal) => ({ + ...(await importOriginal()), + generateUrl: (path, params = {}) => path.replace('{id}', params.id), +})) + +const FILE = `

+

Los +

Werk

Zaken
Script

+

Privé

NOS

+

` + +const stubs = { + NcDialog: { + props: ['open', 'name'], + template: + '

', + }, + NcButton: { + props: ['disabled'], + emits: ['click'], + template: + '', + }, +} + +function settle() { + return new Promise((resolve) => setTimeout(resolve, 0)) +} + +async function chooseFile(wrapper, text, size = text.length) { + const input = wrapper.find('input[type="file"]') + Object.defineProperty(input.element, 'files', { + value: [{ size, text: () => Promise.resolve(text) }], + configurable: true, + }) + await input.trigger('change') + await settle() +} + +describe('BookmarkImportDialog', () => { + beforeEach(() => { + axios.post.mockReset() + }) + + function mountDialog() { + return mount(BookmarkImportDialog, { + props: { open: true, dashboardId: 7 }, + global: { stubs }, + }) + } + + it('REQ-BMI-001: sends only the chosen folder and reports the skipped bookmark', async () => { + axios.post.mockResolvedValue({ + data: { + placements: [{ id: 101, widgetId: 'container' }], + containers: 1, + tiles: 1, + skipped: [ + { + title: 'Script', + url: 'javascript:alert(1)', + reason: 'not-a-web-address', + }, + ], + }, + }) + const wrapper = mountDialog() + await chooseFile(wrapper, FILE) + + const options = wrapper.findAll('.bookmark-import__option') + expect(options.map((option) => option.text())).toEqual([ + 'Werk (2)', + 'Privé (1)', + 'Los', + ]) + const confirm = wrapper.find('[data-testid="bookmark-import-confirm"]') + expect(confirm.attributes('disabled')).toBeDefined() + + await options[0].find('input').setValue(true) + await wrapper + .find('[data-testid="bookmark-import-confirm"]') + .trigger('click') + await settle() + + expect(axios.post).toHaveBeenCalledWith( + '/apps/launchpad/api/dashboard/7/tiles/import', + { + folders: [ + { + name: 'Werk', + bookmarks: [ + { title: 'Zaken', url: 'https://zaken.nl/' }, + { title: 'Script', url: 'javascript:alert(1)' }, + ], + }, + ], + bookmarks: [], + }, + ) + expect(wrapper.text()).toContain('Tiles added: 1') + expect(wrapper.text()).toContain( + '1 bookmark skipped: only web addresses can become tiles', + ) + expect(wrapper.emitted('imported')[0][0]).toEqual([ + { id: 101, widgetId: 'container' }, + ]) + }) + + it('REQ-BMI-003: a full dashboard says how much room is left', async () => { + axios.post.mockRejectedValue({ + response: { + status: 409, + data: { + error: 'quota_exceeded', + quota: 'widgets', + limit: 20, + current: 18, + }, + }, + }) + const wrapper = mountDialog() + await chooseFile(wrapper, FILE) + await wrapper.findAll('.bookmark-import__option input')[0].setValue(true) + await wrapper + .find('[data-testid="bookmark-import-confirm"]') + .trigger('click') + await settle() + + expect(wrapper.find('[role="alert"]').text()).toBe( + 'Room left on this dashboard: 2. Pick fewer folders.', + ) + expect(wrapper.emitted('imported')).toBeUndefined() + }) + + it('REQ-BMI-004: more than 2,000 bookmarks is refused in the browser', async () => { + const many = + '

' + + Array.from( + { length: 2001 }, + (_, i) => `

${i}`, + ).join('') + + '
' + const wrapper = mountDialog() + await chooseFile(wrapper, many) + + expect(wrapper.find('[role="alert"]').text()).toBe( + 'This file has more than 2,000 bookmarks. Export one folder at a time.', + ) + expect(axios.post).not.toHaveBeenCalled() + }) + + it('REQ-BMI-004: a file over 5 MB is refused in the browser', async () => { + const wrapper = mountDialog() + await chooseFile(wrapper, FILE, 6 * 1024 * 1024) + + expect(wrapper.find('[role="alert"]').text()).toBe( + 'This file is larger than 5 MB. Export one folder at a time.', + ) + }) +}) + +describe('Import bookmarks menu entry', () => { + function mountActions(props) { + return mount(DashboardRowActions, { + props: { + dashboard: { id: 7, uuid: 'u', name: 'Mijn werkplek' }, + source: 'user', + ...props, + }, + global: { + stubs: { + NcActions: { template: '
' }, + NcActionButton: { + emits: ['click'], + template: + '', + }, + }, + }, + }) + } + + it('is offered on the active dashboard when the person may add widgets', async () => { + const wrapper = mountActions({ canEdit: true, canImportBookmarks: true }) + const entry = wrapper.find('[data-testid="cog-import-bookmarks"]') + expect(entry.exists()).toBe(true) + await entry.trigger('click') + expect(wrapper.emitted('importBookmarks')).toHaveLength(1) + }) + + it('REQ-BMI-001: a view-only dashboard offers no import', () => { + expect( + mountActions({ canEdit: false, canImportBookmarks: true }) + .find('[data-testid="cog-import-bookmarks"]') + .exists(), + ).toBe(false) + }) +}) diff --git a/src/utils/__tests__/parseBookmarks.spec.js b/src/utils/__tests__/parseBookmarks.spec.js new file mode 100644 index 000000000..0ec0429f8 --- /dev/null +++ b/src/utils/__tests__/parseBookmarks.spec.js @@ -0,0 +1,156 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Vitest unit tests for `parseBookmarks.js` (REQ-BMI-001, REQ-BMI-004) on the + * shapes Firefox, Chrome and Edge export. + */ + +import { describe, expect, it } from 'vitest' +import { + BookmarkFileError, + MAX_BOOKMARK_FILE_BYTES, + parseBookmarksHtml, + readBookmarksFile, +} from '../parseBookmarks.js' + +const HEAD = + '\n\nBookmarks\n

Bookmarks

\n' + +const FIREFOX = + HEAD + + `

+

Mozilla +

Bookmarks Toolbar

+

+

Intranet +

Werk

+

+

Zaaksysteem +

Archief

+

+

Archief +

+

+

+

Other Bookmarks

+

+

Privé

+

+

NOS +
Bookmarklet +

+

+

` + +const CHROME = + HEAD + + `

+

Bookmarks bar

+

+

Gmail +

Werk

+

+

TOPdesk +

+

+

Onderzoek

+

+

Scholar +

+

` + +const EDGE = + HEAD + + `

+

Favorites bar

+

+

Gemeente

+

+

Website +
Raad +

+

+

` + +describe('parseBookmarksHtml', () => { + it('Firefox: toolbar and other bookmarks are transparent, nested folders flatten into their top folder', () => { + const result = parseBookmarksHtml(FIREFOX) + expect(result.total).toBe(6) + expect(result.bookmarks.map((b) => b.title)).toEqual(['Mozilla', 'Intranet']) + expect(result.folders).toEqual([ + { + name: 'Werk', + bookmarks: [ + { title: 'Zaaksysteem', url: 'https://zaken.gemeente.nl/' }, + { title: 'Archief', url: 'https://archief.gemeente.nl/' }, + ], + }, + { + name: 'Privé', + bookmarks: [ + { title: 'NOS', url: 'https://nos.nl/' }, + { title: 'Bookmarklet', url: 'javascript:alert(1)' }, + ], + }, + ]) + }) + + it('Chrome: the bookmarks bar is transparent and a root folder stays a folder', () => { + const result = parseBookmarksHtml(CHROME) + expect(result.bookmarks).toEqual([ + { title: 'Gmail', url: 'https://mail.google.com/' }, + ]) + expect(result.folders.map((f) => [f.name, f.bookmarks.length])).toEqual([ + ['Werk', 1], + ['Onderzoek', 1], + ]) + }) + + it('Edge: the favorites bar is transparent', () => { + const result = parseBookmarksHtml(EDGE) + expect(result.bookmarks).toEqual([]) + expect(result.folders).toEqual([ + { + name: 'Gemeente', + bookmarks: [ + { title: 'Website', url: 'https://www.gemeente.nl/' }, + { title: 'Raad', url: 'https://raad.gemeente.nl/' }, + ], + }, + ]) + }) + + it('REQ-BMI-004: more than the limit is refused', () => { + expect(() => parseBookmarksHtml(CHROME, 2)).toThrow(BookmarkFileError) + try { + parseBookmarksHtml(CHROME, 2) + } catch (error) { + expect(error.code).toBe('too-many') + } + }) + + it('REQ-BMI-004: a file over 5 MB is refused before it is read', async () => { + let read = false + const file = { + size: MAX_BOOKMARK_FILE_BYTES + 1, + text: () => { + read = true + return Promise.resolve('') + }, + } + await expect(readBookmarksFile(file)).rejects.toMatchObject({ + code: 'too-large', + }) + expect(read).toBe(false) + }) + + it('an empty or odd file gives no bookmarks', () => { + expect(parseBookmarksHtml('')).toEqual({ + folders: [], + bookmarks: [], + total: 0, + }) + expect(parseBookmarksHtml('

not bookmarks

').total).toBe(0) + }) +}) diff --git a/src/utils/parseBookmarks.js b/src/utils/parseBookmarks.js new file mode 100644 index 000000000..7659d6d45 --- /dev/null +++ b/src/utils/parseBookmarks.js @@ -0,0 +1,138 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * Parse the bookmarks file browsers export (the Netscape bookmark HTML that + * Firefox, Chrome and Edge all write) in the browser (launcher-bookmark-import, + * REQ-BMI-001, REQ-BMI-004). Nothing is uploaded: the result is a clean list + * the person picks from. + * + * Folders: every bookmark belongs to its outermost folder, so nested folders + * are flattened into their top-level folder. The browsers' own root folders + * (the bookmarks toolbar and "other bookmarks", marked PERSONAL_TOOLBAR_FOLDER + * or UNFILED_BOOKMARKS_FOLDER) are transparent: the folders inside them are + * top-level, and their loose bookmarks are loose. + */ + +export const MAX_BOOKMARK_FILE_BYTES = 5 * 1024 * 1024 + +export const MAX_BOOKMARKS = 2000 + +/** + * Error thrown when a file is over a limit; `code` is `too-large` or `too-many`. + */ +export class BookmarkFileError extends Error { + /** + * @param {string} code `too-large` or `too-many`. + */ + constructor(code) { + super(code) + this.code = code + } +} + +/** + * Whether an H3 folder heading is a browser root folder. + * + * @param {Element} heading The H3. + * @return {boolean} True for the toolbar and unfiled roots. + */ +function isTransparentFolder(heading) { + return ( + heading.hasAttribute('personal_toolbar_folder') + || heading.hasAttribute('unfiled_bookmarks_folder') + ) +} + +/** + * The folder heading a DT carries as a direct child, or null. + * + * @param {Element} dt A DT element. + * @return {Element|null} Its H3. + */ +function headingOf(dt) { + return Array.from(dt.children).find((child) => child.tagName === 'H3') || null +} + +/** + * The name of the outermost real folder that holds an anchor, or null. + * + * @param {Element} anchor An A element. + * @param {Element} root The file's top DL. + * @return {string|null} The folder name. + */ +function outermostFolder(anchor, root) { + let folder = null + for ( + let node = anchor.parentElement; + node && node !== root; + node = node.parentElement + ) { + if (node.tagName !== 'DT') { + continue + } + const heading = headingOf(node) + if (heading && !isTransparentFolder(heading)) { + folder = (heading.textContent || '').trim() + } + } + return folder +} + +/** + * Parse a bookmarks file. + * + * @param {string} html The file's text. + * @param {number} [maxBookmarks] Most bookmarks allowed. + * @return {{folders: Array<{name: string, bookmarks: Array<{title: string, url: string}>}>, bookmarks: Array<{title: string, url: string}>, total: number}} + * The folders in file order, the loose bookmarks, and the count. + * @throws {BookmarkFileError} `too-many` when over the limit. + * @spec openspec/specs/tiles/spec.md + */ +export function parseBookmarksHtml(html, maxBookmarks = MAX_BOOKMARKS) { + const doc = new DOMParser().parseFromString(String(html || ''), 'text/html') + const root = doc.querySelector('dl') || doc.body + const anchors = Array.from(root.querySelectorAll('a[href]')) + if (anchors.length > maxBookmarks) { + throw new BookmarkFileError('too-many') + } + + const folders = new Map() + const loose = [] + anchors.forEach((anchor) => { + const bookmark = { + title: (anchor.textContent || '').trim(), + url: (anchor.getAttribute('href') || '').trim(), + } + const name = outermostFolder(anchor, root) + if (name === null) { + loose.push(bookmark) + return + } + if (!folders.has(name)) { + folders.set(name, []) + } + folders.get(name).push(bookmark) + }) + + return { + folders: Array.from(folders, ([name, bookmarks]) => ({ name, bookmarks })), + bookmarks: loose, + total: anchors.length, + } +} + +/** + * Read and parse a chosen file, refusing one over 5 MB before reading it. + * + * @param {File} file The chosen file. + * @return {Promise} The parsed result, see {@link parseBookmarksHtml}. + * @throws {BookmarkFileError} `too-large` or `too-many`. + * @spec openspec/specs/tiles/spec.md + */ +export async function readBookmarksFile(file) { + if (file.size > MAX_BOOKMARK_FILE_BYTES) { + throw new BookmarkFileError('too-large') + } + return parseBookmarksHtml(await file.text()) +} diff --git a/src/views/Views.vue b/src/views/Views.vue index 2e868431a..a499e16ec 100644 --- a/src/views/Views.vue +++ b/src/views/Views.vue @@ -97,6 +97,7 @@ :canShare="canShareActiveDashboard" :canManagePublication="canManagePublication" :canViewHistory="canViewVersionHistory" + :canImportBookmarks="true" :defaultUuid="defaultDashboardUuid" :isEditMode="isEditMode" :activeDashboardId="activeDashboard.id" @@ -121,6 +122,7 @@ @schedule="scheduleDialogOpen = true" @versionHistory="versionHistoryOpen = true" @menuOpen="checkVersionSupport" + @importBookmarks="bookmarkImportOpen = true" @delete="onSidebarDeleteDashboard(activeDashboard.id)" /> + + + } placements The created placements. + * @spec openspec/specs/tiles/spec.md + */ + onBookmarksImported(placements) { + if (!Array.isArray(placements) || placements.length === 0) { + return + } + const store = useDashboardStore() + store.widgetPlacements = [...store.widgetPlacements, ...placements] + }, + async onRowAddCustomWidget(dashboard, source) { await this.maybeSwitchTo(dashboard.id, source) this.openCustomWidgetModal() diff --git a/tests/Unit/Service/ActionAuthBaselineTest.php b/tests/Unit/Service/ActionAuthBaselineTest.php index 83f46704a..396a95ddc 100644 --- a/tests/Unit/Service/ActionAuthBaselineTest.php +++ b/tests/Unit/Service/ActionAuthBaselineTest.php @@ -125,6 +125,8 @@ class ActionAuthBaselineTest extends TestCase { // The `admin.` prefix is the controller's, not a privilege level: // getMyRole() only ever answers for the caller. 'admin.get-my-role', + // REQ-BMI-001: importing bookmarks is like adding tiles. + 'widget.import-bookmarks', // REQ-PEX-002: every person fills their own profile fields. 'profile-fields.get-own', 'profile-fields.save-own', diff --git a/tests/Unit/Service/BookmarkImportServiceTest.php b/tests/Unit/Service/BookmarkImportServiceTest.php new file mode 100644 index 000000000..3b0edafc8 --- /dev/null +++ b/tests/Unit/Service/BookmarkImportServiceTest.php @@ -0,0 +1,230 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\AdminSettingMapper; +use OCA\LaunchPad\Db\DashboardMapper; +use OCA\LaunchPad\Db\WidgetPlacement; +use OCA\LaunchPad\Db\WidgetPlacementMapper; +use OCA\LaunchPad\Exception\QuotaExceededException; +use OCA\LaunchPad\Service\BookmarkImportService; +use OCA\LaunchPad\Service\PlacementService; +use OCA\LaunchPad\Service\PlacementUpdater; +use OCA\LaunchPad\Service\QuotaService; +use OCA\LaunchPad\Service\TileUpdater; +use OCP\IDBConnection; +use PHPUnit\Framework\TestCase; +use RuntimeException; + +class BookmarkImportServiceTest extends TestCase { + /** + * Inserted placements. + * + * @var WidgetPlacement[] + */ + private array $inserted = []; + + private int $limit = 0; + + private int $existing = 0; + + private $db; + + private ?int $failOnInsert = null; + + private function service(): BookmarkImportService { + $mapper = $this->createMock(WidgetPlacementMapper::class); + $mapper->method('insert')->willReturnCallback( + function (WidgetPlacement $placement): WidgetPlacement { + if ($this->failOnInsert !== null && count($this->inserted) === $this->failOnInsert) { + throw new RuntimeException('database went away'); + } + + $placement->setId(100 + count($this->inserted)); + $this->inserted[] = $placement; + return $placement; + } + ); + $mapper->method('countByDashboardId')->willReturnCallback(fn (): int => $this->existing); + $bottom = new WidgetPlacement(); + $bottom->setGridY(6); + $bottom->setGridHeight(4); + $mapper->method('findByDashboardId')->willReturn([$bottom]); + + $settings = $this->createMock(AdminSettingMapper::class); + $settings->method('getValue')->willReturnCallback( + fn (string $key, mixed $default = null): mixed => $key === 'max_widgets_per_dashboard' ? $this->limit : $default + ); + + $quota = new QuotaService( + settingMapper: $settings, + dashboardMapper: $this->createMock(DashboardMapper::class), + placementMapper: $mapper, + ); + $placements = new PlacementService( + placementMapper: $mapper, + tileUpdater: new TileUpdater(), + placementUpdater: $this->createMock(PlacementUpdater::class), + quotaService: $quota, + ); + + $this->db = $this->createMock(IDBConnection::class); + + return new BookmarkImportService(placements: $placements, placementMapper: $mapper, quota: $quota, db: $this->db); + }//end service() + + private static function bookmarks(string $prefix, int $count): array { + $list = []; + for ($i = 1; $i <= $count; $i++) { + $list[] = ['title' => $prefix . ' ' . $i, 'url' => 'https://example.nl/' . strtolower($prefix) . '/' . $i]; + } + + return $list; + }//end bookmarks() + + /** + * REQ-BMI-001 scenario "Import one folder". + * + * @return void + */ + public function testAFolderBecomesAContainerOfLinkTilesAtTheBottom(): void { + $service = $this->service(); + $this->db->expects($this->once())->method('beginTransaction'); + $this->db->expects($this->once())->method('commit'); + $this->db->expects($this->never())->method('rollBack'); + + $result = $service->import(dashboardId: 7, folders: [['name' => 'Werk', 'bookmarks' => self::bookmarks('Werk', 12)]], bookmarks: []); + + $this->assertCount(1, $this->inserted); + $container = $this->inserted[0]; + $this->assertSame('container', $container->getWidgetId()); + $this->assertSame(10, $container->getGridY(), 'appended below the lowest widget (6 + 4)'); + $content = $container->getContentArray(); + $this->assertSame('Werk', $content['title']); + $this->assertCount(12, $content['placements']); + $first = $content['placements'][0]; + $this->assertSame('link', $first['type']); + $this->assertSame(['label' => 'Werk 1', 'url' => 'https://example.nl/werk/1', 'actionType' => 'external'], array_intersect_key($first['content'], ['label' => 1, 'url' => 1, 'actionType' => 1])); + $this->assertSame([0, 0, 2, 2], [$first['gridX'], $first['gridY'], $first['gridWidth'], $first['gridHeight']]); + $this->assertSame([2, 0], [$content['placements'][1]['gridX'], $content['placements'][1]['gridY']]); + $this->assertSame([0, 2], [$content['placements'][2]['gridX'], $content['placements'][2]['gridY']]); + $this->assertSame(['containers' => 1, 'tiles' => 12, 'skipped' => []], array_intersect_key($result, ['containers' => 1, 'tiles' => 1, 'skipped' => 1])); + }//end testAFolderBecomesAContainerOfLinkTilesAtTheBottom() + + public function testLooseBookmarksBecomeTilesInARow(): void { + $result = $this->service()->import(dashboardId: 7, folders: [], bookmarks: self::bookmarks('Los', 2)); + + $this->assertCount(2, $this->inserted); + $this->assertStringStartsWith('tile-', $this->inserted[0]->getWidgetId()); + $this->assertSame('url', $this->inserted[0]->getTileLinkType()); + $this->assertSame('https://example.nl/los/1', $this->inserted[0]->getTileLinkValue()); + $this->assertSame('Los 1', $this->inserted[0]->getTileTitle()); + $this->assertSame([0, 10], [$this->inserted[0]->getGridX(), $this->inserted[0]->getGridY()]); + $this->assertSame([2, 10], [$this->inserted[1]->getGridX(), $this->inserted[1]->getGridY()]); + $this->assertCount(2, $result['placements']); + }//end testLooseBookmarksBecomeTilesInARow() + + /** + * REQ-BMI-002 scenario "A script bookmark is skipped". + * + * @return void + */ + public function testNonWebAddressesAreSkippedWithAReason(): void { + $bookmarks = self::bookmarks('Werk', 5); + $bookmarks[] = ['title' => 'Alert', 'url' => 'javascript:alert(1)']; + $bookmarks[] = ['title' => 'Local', 'url' => 'file:///etc/passwd']; + $bookmarks[] = ['title' => 'Intranet', 'url' => 'http://intranet.gemeente.local/start']; + + $result = $this->service()->import(dashboardId: 7, folders: [['name' => 'Werk', 'bookmarks' => $bookmarks]], bookmarks: []); + + $this->assertSame(6, $result['tiles'], 'http and https pass, an intranet address included'); + $this->assertSame( + [ + ['title' => 'Alert', 'url' => 'javascript:alert(1)', 'reason' => 'not-a-web-address'], + ['title' => 'Local', 'url' => 'file:///etc/passwd', 'reason' => 'not-a-web-address'], + ], + $result['skipped'] + ); + }//end testNonWebAddressesAreSkippedWithAReason() + + /** + * REQ-BMI-003 scenario "Import too large for the quota". + * + * @return void + */ + public function testTheQuotaIsCheckedOnceForTheWholeImportAndNothingIsCreated(): void { + $this->limit = 20; + $this->existing = 18; + $service = $this->service(); + $this->db->expects($this->never())->method('beginTransaction'); + + try { + $service->import( + dashboardId: 7, + folders: [ + ['name' => 'A', 'bookmarks' => self::bookmarks('A', 1)], + ['name' => 'B', 'bookmarks' => self::bookmarks('B', 1)], + ['name' => 'C', 'bookmarks' => self::bookmarks('C', 1)], + ], + bookmarks: [] + ); + $this->fail('the import passed the quota'); + } catch (QuotaExceededException $e) { + $this->assertSame(['limit' => 20, 'current' => 18], array_intersect_key($e->toResponseBody(), ['limit' => 1, 'current' => 1])); + } + + $this->assertSame([], $this->inserted); + }//end testTheQuotaIsCheckedOnceForTheWholeImportAndNothingIsCreated() + + public function testAFailurePartWayRollsBack(): void { + $service = $this->service(); + $this->failOnInsert = 1; + $this->db->expects($this->once())->method('beginTransaction'); + $this->db->expects($this->once())->method('rollBack'); + $this->db->expects($this->never())->method('commit'); + + $this->expectException(RuntimeException::class); + $service->import(dashboardId: 7, folders: [['name' => 'A', 'bookmarks' => self::bookmarks('A', 1)]], bookmarks: self::bookmarks('Los', 1)); + }//end testAFailurePartWayRollsBack() + + public function testMoreThanTwoThousandBookmarksIsRefused(): void { + $this->expectException(InvalidArgumentException::class); + $this->service()->import(dashboardId: 7, folders: [['name' => 'Big', 'bookmarks' => self::bookmarks('B', 2001)]], bookmarks: []); + }//end testMoreThanTwoThousandBookmarksIsRefused() + + public function testNestedFolderNamesAreNotTrustedAndEmptyFoldersAreDropped(): void { + $result = $this->service()->import( + dashboardId: 7, + folders: [ + ['name' => '', 'bookmarks' => self::bookmarks('X', 1)], + ['name' => 'Leeg', 'bookmarks' => [['title' => 'Bad', 'url' => 'ftp://x']]], + ], + bookmarks: [] + ); + + $this->assertSame(1, $result['containers']); + $this->assertSame('Bookmarks', $this->inserted[0]->getContentArray()['title']); + }//end testNestedFolderNamesAreNotTrustedAndEmptyFoldersAreDropped() +}//end class From 061ca8a8f189ccea4eeb9c003c867877ab3543bc Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 09:14:41 +0200 Subject: [PATCH 08/21] docs(openspec): archive launcher-bookmark-import, d-import-bookmarks is built --- .../.openspec.yaml | 0 .../design.md | 8 +++ .../proposal.md | 0 .../specs/tiles/spec.md | 0 .../tasks.md | 14 ++--- openspec/parity/capabilities.json | 7 ++- openspec/specs/tiles/spec.md | 56 +++++++++++++++++++ 7 files changed, 75 insertions(+), 10 deletions(-) rename openspec/changes/{launcher-bookmark-import => archive/2026-09-30-launcher-bookmark-import}/.openspec.yaml (100%) rename openspec/changes/{launcher-bookmark-import => archive/2026-09-30-launcher-bookmark-import}/design.md (71%) rename openspec/changes/{launcher-bookmark-import => archive/2026-09-30-launcher-bookmark-import}/proposal.md (100%) rename openspec/changes/{launcher-bookmark-import => archive/2026-09-30-launcher-bookmark-import}/specs/tiles/spec.md (100%) rename openspec/changes/{launcher-bookmark-import => archive/2026-09-30-launcher-bookmark-import}/tasks.md (61%) diff --git a/openspec/changes/launcher-bookmark-import/.openspec.yaml b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/.openspec.yaml similarity index 100% rename from openspec/changes/launcher-bookmark-import/.openspec.yaml rename to openspec/changes/archive/2026-09-30-launcher-bookmark-import/.openspec.yaml diff --git a/openspec/changes/launcher-bookmark-import/design.md b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/design.md similarity index 71% rename from openspec/changes/launcher-bookmark-import/design.md rename to openspec/changes/archive/2026-09-30-launcher-bookmark-import/design.md index f6747d7ae..1448951c7 100644 --- a/openspec/changes/launcher-bookmark-import/design.md +++ b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/design.md @@ -42,3 +42,11 @@ The user needs `add_only` or `full` permission on the dashboard (`canAddWidget() - Vitest: the parser on exports from Firefox, Chrome and Edge fixtures, including nested folders and non-http links. - PHPUnit: endpoint auth, quota refusal (409, nothing created), unsafe URLs skipped, transaction rollback on a mid-import failure. - Playwright: import a fixture file, pick one folder, see one container with its tiles at the bottom of the dashboard. + +## What the build corrected (2026-09-30) + +- D3: `UrlSafetyValidator::isSafe()` is an SSRF check (https only, public addresses only, a DNS lookup per host). A tile never makes the server fetch its address, and intranet bookmarks are the point, so the import accepts http and https addresses with a host (`BookmarkImportService::isWebAddress()`) and skips the rest with the reason `not-a-web-address`. +- The endpoint lives in its own `BookmarkImportController` and `BookmarkImportService` rather than in `WidgetApiController` and `PlacementService`, which already carry many collaborators. The quota check for the whole import is `QuotaService::assertRoomFor()`; every placement still passes the single check inside `PlacementService`. +- Folder tiles are `link` widgets inside the container (the container renders children through the widget registry); loose bookmarks are URL tiles (`addTileFromArray`, link type `url`). Containers are 4 columns wide, side by side, below the lowest widget; loose tiles follow in rows of six. +- The browsers' own root folders (bookmarks toolbar, other bookmarks, favorites bar) are transparent: the folders inside them are the top-level folders. +- The dialog is `src/dialogs/BookmarkImportDialog.vue` (an NcDialog, per the modal-isolation rule), opened from "Import bookmarks…" in the active dashboard's menu, shown only with add rights. diff --git a/openspec/changes/launcher-bookmark-import/proposal.md b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/proposal.md similarity index 100% rename from openspec/changes/launcher-bookmark-import/proposal.md rename to openspec/changes/archive/2026-09-30-launcher-bookmark-import/proposal.md diff --git a/openspec/changes/launcher-bookmark-import/specs/tiles/spec.md b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/specs/tiles/spec.md similarity index 100% rename from openspec/changes/launcher-bookmark-import/specs/tiles/spec.md rename to openspec/changes/archive/2026-09-30-launcher-bookmark-import/specs/tiles/spec.md diff --git a/openspec/changes/launcher-bookmark-import/tasks.md b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/tasks.md similarity index 61% rename from openspec/changes/launcher-bookmark-import/tasks.md rename to openspec/changes/archive/2026-09-30-launcher-bookmark-import/tasks.md index 23b255562..095a2e5ea 100644 --- a/openspec/changes/launcher-bookmark-import/tasks.md +++ b/openspec/changes/archive/2026-09-30-launcher-bookmark-import/tasks.md @@ -1,9 +1,9 @@ # Tasks: launcher-bookmark-import -- [ ] 1. Seed `widget.import-bookmarks` as `["admin", "@all"]` in `lib/actions.seed.json` and add the route `POST /api/dashboard/{dashboardId}/tiles/import`. Verify: route-reachability and action-matrix tests. -- [ ] 2. Implement `WidgetApiController::importBookmarks()` with the action check, `canAddWidget()` and 409 on quota. Verify: PHPUnit for 401, 403, 409 and 201. -- [ ] 3. Add `PlacementService::importBookmarks()`: URL check per bookmark, one quota check, containers for folders, bottom-append, one transaction, a skipped list in the response. Verify: PHPUnit including rollback. -- [ ] 4. Write the browser parser for the Netscape bookmark format with size and count limits. Verify: Vitest on Firefox, Chrome and Edge fixtures. -- [ ] 5. Build `src/modals/BookmarkImportModal.vue` (file picker, folder and bookmark checkboxes, skipped list) and the "Import bookmarks" entry in the dashboard menu. Verify: Vitest and an axe check. -- [ ] 6. Playwright: import a fixture, pick one folder, see its container at the bottom. -- [ ] 7. Add `nl` and `en` strings, fold the delta into `openspec/specs/tiles/spec.md` on archive, and set d-import-bookmarks to `built` in the parity matrix with evidence lines. +- [x] 1. Seed `widget.import-bookmarks` as `["admin", "@all"]` in `lib/actions.seed.json` and add the route `POST /api/dashboard/{dashboardId}/tiles/import`. Verify: route-reachability and action-matrix tests. +- [x] 2. Implement `WidgetApiController::importBookmarks()` with the action check, `canAddWidget()` and 409 on quota. Verify: PHPUnit for 401, 403, 409 and 201. +- [x] 3. Add `PlacementService::importBookmarks()`: URL check per bookmark, one quota check, containers for folders, bottom-append, one transaction, a skipped list in the response. Verify: PHPUnit including rollback. +- [x] 4. Write the browser parser for the Netscape bookmark format with size and count limits. Verify: Vitest on Firefox, Chrome and Edge fixtures. +- [x] 5. Build `src/modals/BookmarkImportModal.vue` (file picker, folder and bookmark checkboxes, skipped list) and the "Import bookmarks" entry in the dashboard menu. Verify: Vitest and an axe check. +- [ ] 6. Playwright: import a fixture, pick one folder, see its container at the bottom. Not written: the requirements carry `@e2e exclude` naming the PHPUnit and Vitest files that cover them. +- [x] 7. Add `nl` and `en` strings, fold the delta into `openspec/specs/tiles/spec.md` on archive, and set d-import-bookmarks to `built` in the parity matrix with evidence lines. diff --git a/openspec/parity/capabilities.json b/openspec/parity/capabilities.json index 09332f820..39c87dc5a 100644 --- a/openspec/parity/capabilities.json +++ b/openspec/parity/capabilities.json @@ -4827,11 +4827,12 @@ "homarr": "no", "dashy": "no", "built": { - "state": "specified", + "state": "built", + "evidence": "lib/Service/BookmarkImportService.php; lib/Controller/BookmarkImportController.php; src/utils/parseBookmarks.js; src/dialogs/BookmarkImportDialog.vue", "owner": "ConductionNL/launchpad", - "change": "openspec/changes/launcher-bookmark-import" + "change": "openspec/changes/archive/2026-09-30-launcher-bookmark-import" }, - "reachedOn": "nothing", + "reachedOn": "Active dashboard menu (cog) > Import bookmarks…, on a dashboard the person may add widgets to", "evidence": { "workspace-365": "not checked: user guide 175363 and FAQ for users 175611 cover creating shortcut tiles by hand ('+ Add tiles') but no import of browser bookmarks (read 2026-09-26)", "microsoft-viva": "not checked: https://learn.microsoft.com/en-us/sharepoint/homesites/available-dashboard-cards Quick links card takes links added one by one ('+ Add link', up to 20); no bookmark import found (read 2026-09-26)", diff --git a/openspec/specs/tiles/spec.md b/openspec/specs/tiles/spec.md index 89659d79d..b01b85e97 100644 --- a/openspec/specs/tiles/spec.md +++ b/openspec/specs/tiles/spec.md @@ -343,6 +343,62 @@ A future migration MAY rewrite legacy placements into the new content shape, but - **THEN** the resulting `oc_launchpad_widget_placements` row MUST have its data in `content` (JSON column) - **AND** the legacy flat `tileTitle`/`tileIcon`/etc. columns MUST NOT be populated for new placements +### Requirement: Users can import a browser bookmarks file as tiles (REQ-BMI-001) + +@e2e exclude Playwright is not wired for the bookmark import yet; covered by tests/Unit/Service/BookmarkImportServiceTest.php, src/utils/__tests__/parseBookmarks.spec.js and src/dialogs/__tests__/BookmarkImportDialog.spec.js + +A user with add rights on a dashboard MUST be able to choose a bookmarks file in the HTML format browsers export, see its folders and bookmarks, pick which to import, and have them added as tiles. Each chosen top-level folder MUST become a container titled with the folder name holding its bookmarks as tiles, with nested folders flattened into it. Loose bookmarks MUST become tiles on the dashboard. New items MUST be appended at the bottom of the dashboard. + +#### Scenario: Import one folder + +- **GIVEN** Pieter has full permission on "Mijn werkplek" and a bookmarks file with folders "Werk" (12 bookmarks) and "Privé" (30 bookmarks) +- **WHEN** he chooses "Import bookmarks", selects the file, ticks only "Werk" and confirms +- **THEN** a container "Werk" with 12 tiles appears at the bottom of "Mijn werkplek" +- **AND** nothing from "Privé" is imported + +#### Scenario: View-only dashboard offers no import + +- **GIVEN** Pieter views the group dashboard "Organisatie" with view-only permission +- **WHEN** he opens the dashboard menu +- **THEN** "Import bookmarks" is not in the menu + +### Requirement: The import checks every address (REQ-BMI-002) + +@e2e exclude Playwright is not wired for the bookmark import yet; covered by tests/Unit/Service/BookmarkImportServiceTest.php, src/utils/__tests__/parseBookmarks.spec.js and src/dialogs/__tests__/BookmarkImportDialog.spec.js + +The server MUST import only `http` and `https` addresses that pass the shared URL safety check. Other bookmarks MUST be skipped and listed back to the user with the reason. + +#### Scenario: A script bookmark is skipped + +- **GIVEN** the chosen folder holds a bookmarklet `javascript:alert(1)` and 5 normal bookmarks +- **WHEN** Pieter confirms the import +- **THEN** 5 tiles are created +- **AND** the result says "1 bookmark skipped: only web addresses can become tiles" + +### Requirement: The import respects the widget quota as one operation (REQ-BMI-003) + +@e2e exclude Playwright is not wired for the bookmark import yet; covered by tests/Unit/Service/BookmarkImportServiceTest.php, src/utils/__tests__/parseBookmarks.spec.js and src/dialogs/__tests__/BookmarkImportDialog.spec.js + +The server MUST check the dashboard's widget quota for the whole import before creating anything, and MUST create all items in one transaction. When the quota would be exceeded it MUST return HTTP 409 with the quota body and create nothing. + +#### Scenario: Import too large for the quota + +- **GIVEN** the administrator limits dashboards to 20 widgets and "Mijn werkplek" has 18 +- **WHEN** Pieter imports 3 folders +- **THEN** the response is 409, the dialog says the dashboard has room for 2 more items, and no container or tile is created + +### Requirement: Large files are refused in the browser (REQ-BMI-004) + +@e2e exclude Playwright is not wired for the bookmark import yet; covered by tests/Unit/Service/BookmarkImportServiceTest.php, src/utils/__tests__/parseBookmarks.spec.js and src/dialogs/__tests__/BookmarkImportDialog.spec.js + +The browser MUST refuse bookmark files over 5 MB or with more than 2,000 bookmarks before sending anything, with a message naming the limit. + +#### Scenario: Oversized file + +- **GIVEN** a bookmarks file with 3,500 bookmarks +- **WHEN** Pieter selects it +- **THEN** the dialog says "This file has more than 2,000 bookmarks. Export one folder at a time." and nothing is sent to the server + ## Non-Functional Requirements - **Performance**: GET /api/tiles MUST return within 300ms for users with up to 100 tiles. From 5f8e334efea5805df28b56f96614a44888623abd Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 09:19:36 +0200 Subject: [PATCH 09/21] feat(tiles): administrators list the office networks, and a tile may carry an address for inside the office office_networks holds IPv4 and IPv6 ranges, validated with Nextcloud's IP factory; the server decides from the request address and tells the page onOfficeNetwork. A tile's content.internalUrl must be an http or https address or a path on this Nextcloud, or the update is refused. REQ-TIA-001, REQ-TIA-002 (launcher-tile-internal-address) --- appinfo/routes.php | 4 + lib/Controller/OfficeNetworksController.php | 147 +++++++++++++++ lib/Controller/PageController.php | 6 + lib/Db/AdminSettingKey.php | 2 + lib/Service/InitialStateBuilder.php | 16 ++ lib/Service/OfficeNetworkService.php | 176 ++++++++++++++++++ lib/Service/TileUpdater.php | 40 ++++ lib/actions.seed.json | 2 + src/utils/loadInitialState.js | 4 + .../PageControllerBridgedWidgetsTest.php | 1 + tests/Unit/Database/OfficeNetworkLiveTest.php | 43 +++++ tests/Unit/Service/ActionAuthBaselineTest.php | 2 + .../Unit/Service/OfficeNetworkServiceTest.php | 107 +++++++++++ .../Unit/Service/TileInternalAddressTest.php | 105 +++++++++++ tests/Unit/Support/CidrIpFactory.php | 119 ++++++++++++ 15 files changed, 774 insertions(+) create mode 100644 lib/Controller/OfficeNetworksController.php create mode 100644 lib/Service/OfficeNetworkService.php create mode 100644 tests/Unit/Database/OfficeNetworkLiveTest.php create mode 100644 tests/Unit/Service/OfficeNetworkServiceTest.php create mode 100644 tests/Unit/Service/TileInternalAddressTest.php create mode 100644 tests/Unit/Support/CidrIpFactory.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 4c8f64225..ebbe96e01 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -461,6 +461,10 @@ ['name' => 'searchShortcuts#index', 'url' => '/api/admin/search-shortcuts', 'verb' => 'GET'], ['name' => 'searchShortcuts#save', 'url' => '/api/admin/search-shortcuts', 'verb' => 'PUT'], + // Office networks (launcher-tile-internal-address REQ-TIA-001): admin list. + ['name' => 'officeNetworks#index', 'url' => '/api/admin/office-networks', 'verb' => 'GET'], + ['name' => 'officeNetworks#save', 'url' => '/api/admin/office-networks', 'verb' => 'PUT'], + // Custom profile fields (REQ-PEX-001, REQ-PEX-002): own values for // every signed-in person, definitions for administrators. ['name' => 'profileFields#getOwn', 'url' => '/api/profile-fields/me', 'verb' => 'GET'], diff --git a/lib/Controller/OfficeNetworksController.php b/lib/Controller/OfficeNetworksController.php new file mode 100644 index 000000000..59693872d --- /dev/null +++ b/lib/Controller/OfficeNetworksController.php @@ -0,0 +1,147 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version GIT:auto + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Controller; + +use InvalidArgumentException; +use OCA\LaunchPad\AppInfo\Application; +use OCA\LaunchPad\Service\ActionAuthService; +use OCA\LaunchPad\Service\OfficeNetworkService; +use OCA\LaunchPad\Settings\LaunchPadAdmin; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\IGroupManager; +use OCP\IRequest; +use OCP\IUserSession; + +/** + * Office network administration. + * + * @spec openspec/specs/tiles/spec.md + */ +class OfficeNetworksController extends Controller { + /** + * Constructor. + * + * @param IRequest $request HTTP request. + * @param OfficeNetworkService $officeNetworks Office network service. + * @param ActionAuthService $actionAuth ADR-023 action authorization. + * @param IUserSession $userSession Current user. + * @param IGroupManager $groupManager Admin check. + * + * @spec openspec/specs/tiles/spec.md + */ + public function __construct( + IRequest $request, + private readonly OfficeNetworkService $officeNetworks, + private readonly ActionAuthService $actionAuth, + private readonly IUserSession $userSession, + private readonly IGroupManager $groupManager, + ) { + parent::__construct( + appName: Application::APP_ID, + request: $request + ); + }//end __construct() + + /** + * The ranges and the caller's own address (administrators). + * + * @return JSONResponse `{ranges, currentAddress, onOfficeNetwork}`, 401 or 403. + * + * @spec openspec/specs/tiles/spec.md + */ + #[AuthorizedAdminSetting(LaunchPadAdmin::class)] + public function index(): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse(data: ['error' => 'Not authenticated'], statusCode: Http::STATUS_UNAUTHORIZED); + } + + if ($this->groupManager->isAdmin(userId: $user->getUID()) === false) { + return new JSONResponse(data: ['error' => 'Admin required'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $this->actionAuth->requireAction($user, 'office-network.list'); + } catch (OCSForbiddenException) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + return new JSONResponse(data: $this->state(ranges: $this->officeNetworks->getRanges())); + }//end index() + + /** + * Replace the ranges (administrators). + * + * @param array|null $ranges IPv4 or IPv6 ranges in CIDR notation. + * + * @return JSONResponse `{ranges, currentAddress, onOfficeNetwork}`, 400, 401 or 403. + * + * @spec openspec/specs/tiles/spec.md + */ + #[AuthorizedAdminSetting(LaunchPadAdmin::class)] + public function save(?array $ranges = null): JSONResponse { + $user = $this->userSession->getUser(); + if ($user === null) { + return new JSONResponse(data: ['error' => 'Not authenticated'], statusCode: Http::STATUS_UNAUTHORIZED); + } + + if ($this->groupManager->isAdmin(userId: $user->getUID()) === false) { + return new JSONResponse(data: ['error' => 'Admin required'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $this->actionAuth->requireAction($user, 'office-network.save'); + } catch (OCSForbiddenException) { + return new JSONResponse(data: ['error' => 'Forbidden'], statusCode: Http::STATUS_FORBIDDEN); + } + + try { + $saved = $this->officeNetworks->saveRanges(raw: ($ranges ?? [])); + } catch (InvalidArgumentException $e) { + return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: Http::STATUS_BAD_REQUEST); + } + + return new JSONResponse(data: $this->state(ranges: $saved)); + }//end save() + + /** + * The response body. + * + * @param string[] $ranges The ranges. + * + * @return array{ranges: string[], currentAddress: string, onOfficeNetwork: bool} + */ + private function state(array $ranges): array { + return [ + 'ranges' => $ranges, + 'currentAddress' => $this->officeNetworks->currentAddress(), + 'onOfficeNetwork' => $this->officeNetworks->isOfficeRequest(), + ]; + }//end state() +}//end class diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 6497dffac..9886226e9 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -34,6 +34,7 @@ use OCA\LaunchPad\Service\InitialState\Page; use OCA\LaunchPad\Service\InitialStateBuilder; use OCA\LaunchPad\Service\RoleFeaturePermissionService; +use OCA\LaunchPad\Service\OfficeNetworkService; use OCA\LaunchPad\Service\SearchShortcutService; use OCA\LaunchPad\Service\WidgetService; use OCP\AppFramework\Controller; @@ -95,6 +96,9 @@ class PageController extends Controller { * @param SearchShortcutService $searchShortcuts The search shortcuts every * search box honours * (REQ-SPX-002). + * @param OfficeNetworkService $officeNetworks Whether the request comes + * from an office network + * (REQ-TIA-002). */ public function __construct( IRequest $request, @@ -108,6 +112,7 @@ public function __construct( private readonly LoggerInterface $logger, private readonly AdminSettingsService $adminSettingsService, private readonly SearchShortcutService $searchShortcuts, + private readonly OfficeNetworkService $officeNetworks, ) { parent::__construct(appName: Application::APP_ID, request: $request); }//end __construct() @@ -230,6 +235,7 @@ public function index(string $deepLink = ''): TemplateResponse { ->setDeepLinkPath($activeState['deepLinkPath']) ->setQuicksearchFallbackTarget($quicksearchFallback) ->setSearchShortcuts($this->searchShortcuts->getShortcuts()) + ->setOnOfficeNetwork($this->officeNetworks->isOfficeRequest()) ->apply(); // 🔴 NO CHROME SLOT IDS. This used to pass diff --git a/lib/Db/AdminSettingKey.php b/lib/Db/AdminSettingKey.php index f56c35290..32f043627 100644 --- a/lib/Db/AdminSettingKey.php +++ b/lib/Db/AdminSettingKey.php @@ -18,6 +18,7 @@ * - Setup wizard: SETUP_WIZARD_COMPLETE, CONTENT_STORAGE * - Quick search: QUICKSEARCH_FALLBACK_TARGET (tile-quick-search), * SEARCH_SHORTCUTS (search-ai-prefix-shortcuts) + * - Tiles: OFFICE_NETWORKS (launcher-tile-internal-address) * * @category Db * @package OCA\LaunchPad\Db @@ -64,4 +65,5 @@ enum AdminSettingKey: string { case MAX_WIDGETS_PER_DASHBOARD = 'max_widgets_per_dashboard'; case QUICKSEARCH_FALLBACK_TARGET = 'quicksearch_fallback_target'; case SEARCH_SHORTCUTS = 'search_shortcuts'; + case OFFICE_NETWORKS = 'office_networks'; }//end enum diff --git a/lib/Service/InitialStateBuilder.php b/lib/Service/InitialStateBuilder.php index 0c3903dd3..1a25758cd 100644 --- a/lib/Service/InitialStateBuilder.php +++ b/lib/Service/InitialStateBuilder.php @@ -354,6 +354,22 @@ public function setSearchShortcuts(array $shortcuts): self { return $this; }//end setSearchShortcuts() + /** + * Say whether this request comes from an office network, so tiles open + * their internal address (launcher-tile-internal-address, REQ-TIA-002). + * Optional key; the JS reader defaults it to false. + * + * @param bool $onOfficeNetwork Whether the request is on an office network. + * + * @return self Fluent. + * + * @spec openspec/specs/tiles/spec.md + */ + public function setOnOfficeNetwork(bool $onOfficeNetwork): self { + $this->values['onOfficeNetwork'] = $onOfficeNetwork; + return $this; + }//end setOnOfficeNetwork() + /** * Set every Nextcloud group (admin). * diff --git a/lib/Service/OfficeNetworkService.php b/lib/Service/OfficeNetworkService.php new file mode 100644 index 000000000..5ab632bc3 --- /dev/null +++ b/lib/Service/OfficeNetworkService.php @@ -0,0 +1,176 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\LaunchPad\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\AdminSettingKey; +use OCA\LaunchPad\Db\AdminSettingMapper; +use OCP\IRequest; +use OCP\Security\Ip\IFactory; +use Throwable; + +/** + * Office network ranges and the request check. + * + * @spec openspec/specs/tiles/spec.md + */ +class OfficeNetworkService { + /** + * Most ranges an administrator may list. + * + * @var int + */ + public const MAX_RANGES = 50; + + /** + * Constructor. + * + * @param AdminSettingMapper $settingMapper Admin settings store. + * @param IRequest $request The current request. + * @param IFactory $ipFactory Parses addresses and ranges. + * + * @spec openspec/specs/tiles/spec.md + */ + public function __construct( + private readonly AdminSettingMapper $settingMapper, + private readonly IRequest $request, + private readonly IFactory $ipFactory, + ) { + }//end __construct() + + /** + * The stored ranges; an unparsable stored range is left out. + * + * @return string[] + * + * @spec openspec/specs/tiles/spec.md + */ + public function getRanges(): array { + $raw = $this->settingMapper->getValue(key: AdminSettingKey::OFFICE_NETWORKS->value, default: []); + if (is_array(value: $raw) === false) { + return []; + } + + $ranges = []; + foreach ($raw as $range) { + if (is_string(value: $range) === true && $this->isValidRange(range: $range) === true) { + $ranges[] = $range; + } + } + + return $ranges; + }//end getRanges() + + /** + * Validate and store the ranges (IPv4 or IPv6 CIDR, or a single address). + * + * @param array $raw The ranges. + * + * @return string[] The stored ranges. + * + * @throws InvalidArgumentException When a range is malformed or there are too many. + * + * @spec openspec/specs/tiles/spec.md + */ + public function saveRanges(array $raw): array { + $ranges = []; + foreach ($raw as $range) { + $range = trim(string: (string)$range); + if ($range === '') { + continue; + } + + if ($this->isValidRange(range: $range) === false) { + throw new InvalidArgumentException(message: 'Not an IP range: ' . mb_substr(string: $range, start: 0, length: 64)); + } + + $ranges[$range] = $range; + } + + if (count(value: $ranges) > self::MAX_RANGES) { + throw new InvalidArgumentException(message: 'At most ' . self::MAX_RANGES . ' office network ranges'); + } + + $ranges = array_values(array: $ranges); + $this->settingMapper->setSetting(key: AdminSettingKey::OFFICE_NETWORKS->value, value: $ranges); + + return $ranges; + }//end saveRanges() + + /** + * Whether the current request comes from an office network. + * + * @return bool + * + * @spec openspec/specs/tiles/spec.md + */ + public function isOfficeRequest(): bool { + $ranges = $this->getRanges(); + if ($ranges === []) { + return false; + } + + try { + $address = $this->ipFactory->addressFromString(ip: $this->currentAddress()); + } catch (Throwable) { + return false; + } + + foreach ($ranges as $range) { + if ($this->ipFactory->rangeFromString(range: $range)->contains(address: $address) === true) { + return true; + } + } + + return false; + }//end isOfficeRequest() + + /** + * The address the current request comes from, as Nextcloud sees it + * (trusted proxies applied). + * + * @return string + * + * @spec openspec/specs/tiles/spec.md + */ + public function currentAddress(): string { + return $this->request->getRemoteAddress(); + }//end currentAddress() + + /** + * Whether a string parses as a range. + * + * @param string $range The candidate. + * + * @return bool + */ + private function isValidRange(string $range): bool { + try { + $this->ipFactory->rangeFromString(range: $range); + return true; + } catch (Throwable) { + return false; + } + }//end isValidRange() +}//end class diff --git a/lib/Service/TileUpdater.php b/lib/Service/TileUpdater.php index 133e6acd5..8c0370171 100644 --- a/lib/Service/TileUpdater.php +++ b/lib/Service/TileUpdater.php @@ -18,6 +18,7 @@ namespace OCA\LaunchPad\Service; +use InvalidArgumentException; use OCA\LaunchPad\Db\WidgetPlacement; /** @@ -76,6 +77,13 @@ public function applyTileUpdates( WidgetPlacement $placement, array $data, ): void { + // REQ-TIA-002: the address on the office network follows the main + // address rules; anything else refuses the whole update. + $content = $data['content'] ?? null; + if (is_array(value: $content) === true && array_key_exists(key: 'internalUrl', array: $content) === true) { + self::assertValidInternalUrl(value: $content['internalUrl']); + } + if (isset($data['tileTitle']) === true) { $placement->setTileTitle($data['tileTitle']); } @@ -114,4 +122,36 @@ public function applyTileUpdates( ); } }//end applyTileUpdates() + + /** + * Check a tile's address on the office network: empty, an http or https + * address with a host, or a path on this Nextcloud (REQ-TIA-002). + * + * @param mixed $value The submitted value. + * + * @return void + * + * @throws InvalidArgumentException When the value is not such an address. + * + * @spec openspec/specs/tiles/spec.md + */ + public static function assertValidInternalUrl(mixed $value): void { + if ($value === null || $value === '') { + return; + } + + if (is_string(value: $value) === false || strlen(string: $value) > 2048) { + throw new InvalidArgumentException(message: 'The office network address must be an http or https address, or a path'); + } + + if (str_starts_with(haystack: $value, needle: '/') === true && str_starts_with(haystack: $value, needle: '//') === false) { + return; + } + + $scheme = strtolower(string: (string)parse_url(url: $value, component: PHP_URL_SCHEME)); + $host = (string)parse_url(url: $value, component: PHP_URL_HOST); + if (($scheme !== 'http' && $scheme !== 'https') || $host === '') { + throw new InvalidArgumentException(message: 'The office network address must be an http or https address, or a path'); + } + }//end assertValidInternalUrl() }//end class diff --git a/lib/actions.seed.json b/lib/actions.seed.json index dc9b9436d..5694f036e 100644 --- a/lib/actions.seed.json +++ b/lib/actions.seed.json @@ -62,6 +62,8 @@ "people-widget.get-users": ["admin"], "search-shortcut.list": ["admin"], "search-shortcut.save": ["admin"], + "office-network.list": ["admin"], + "office-network.save": ["admin"], "profile-fields.get-own": ["admin", "@all"], "profile-fields.save-own": ["admin", "@all"], "profile-fields.get-definitions": ["admin"], diff --git a/src/utils/loadInitialState.js b/src/utils/loadInitialState.js index 4c65b0b45..ac7b12eb8 100644 --- a/src/utils/loadInitialState.js +++ b/src/utils/loadInitialState.js @@ -67,6 +67,10 @@ const PAGE_KEYS = { // shortcuts `{prefix, name, urlTemplate}`. Optional key; an empty // list keeps every search box as it was. searchShortcuts: [], + // launcher-tile-internal-address REQ-TIA-002: whether this request + // comes from an office network, so tiles open their internal + // address. Optional key; false keeps every tile on its normal address. + onOfficeNetwork: false, }, admin: { allGroups: [], diff --git a/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php b/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php index de29ee609..3edd72a43 100644 --- a/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php +++ b/tests/Unit/Controller/PageControllerBridgedWidgetsTest.php @@ -65,6 +65,7 @@ private function makeController(WidgetService $widgetService): PageController { logger: $this->createMock(LoggerInterface::class), adminSettingsService: $this->createMock(AdminSettingsService::class), searchShortcuts: $this->createMock(\OCA\LaunchPad\Service\SearchShortcutService::class), + officeNetworks: $this->createMock(\OCA\LaunchPad\Service\OfficeNetworkService::class), ); }//end makeController() diff --git a/tests/Unit/Database/OfficeNetworkLiveTest.php b/tests/Unit/Database/OfficeNetworkLiveTest.php new file mode 100644 index 000000000..187cc0953 --- /dev/null +++ b/tests/Unit/Database/OfficeNetworkLiveTest.php @@ -0,0 +1,43 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Database; + +use OCP\Security\Ip\IFactory; +use OCP\Server; + +/** + * @SuppressWarnings(PHPMD.StaticAccess) Server::get() is the only way in from a test. + */ +class OfficeNetworkLiveTest extends RealDatabaseTestCase { + /** + * The ranges the service stores parse and match with the server's factory. + * + * @return void + */ + public function testTheServerFactoryAgreesWithTheRangesWeStore(): void { + $factory = Server::get(IFactory::class); + + $this->assertTrue($factory->rangeFromString('10.0.0.0/8')->contains($factory->addressFromString('10.20.30.40'))); + $this->assertFalse($factory->rangeFromString('192.168.12.0/24')->contains($factory->addressFromString('192.168.13.1'))); + $this->assertTrue($factory->rangeFromString('2001:db8:12::/48')->contains($factory->addressFromString('2001:db8:12:ff::1'))); + }//end testTheServerFactoryAgreesWithTheRangesWeStore() +}//end class diff --git a/tests/Unit/Service/ActionAuthBaselineTest.php b/tests/Unit/Service/ActionAuthBaselineTest.php index 396a95ddc..172491c53 100644 --- a/tests/Unit/Service/ActionAuthBaselineTest.php +++ b/tests/Unit/Service/ActionAuthBaselineTest.php @@ -86,6 +86,8 @@ class ActionAuthBaselineTest extends TestCase { 'people-widget.get-users', 'search-shortcut.list', 'search-shortcut.save', + 'office-network.list', + 'office-network.save', 'profile-fields.get-definitions', 'profile-fields.save-definitions', 'rule.get-rules', diff --git a/tests/Unit/Service/OfficeNetworkServiceTest.php b/tests/Unit/Service/OfficeNetworkServiceTest.php new file mode 100644 index 000000000..04d3ac9f2 --- /dev/null +++ b/tests/Unit/Service/OfficeNetworkServiceTest.php @@ -0,0 +1,107 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\AdminSetting; +use OCA\LaunchPad\Db\AdminSettingMapper; +use OCA\LaunchPad\Service\OfficeNetworkService; +use OCP\IRequest; +use PHPUnit\Framework\TestCase; +use Unit\Support\CidrIpFactory; + +class OfficeNetworkServiceTest extends TestCase { + /** + * Stored admin settings. + * + * @var array + */ + private array $settings = []; + + private string $remote = '203.0.113.9'; + + private function service(): OfficeNetworkService { + $mapper = $this->createMock(AdminSettingMapper::class); + $mapper->method('getValue')->willReturnCallback( + fn (string $key, mixed $default = null): mixed => $this->settings[$key] ?? $default + ); + $mapper->method('setSetting')->willReturnCallback( + function (string $key, mixed $value): AdminSetting { + $this->settings[$key] = $value; + return new AdminSetting(); + } + ); + $request = $this->createMock(IRequest::class); + $request->method('getRemoteAddress')->willReturnCallback(fn (): string => $this->remote); + + return new OfficeNetworkService(settingMapper: $mapper, request: $request, ipFactory: new CidrIpFactory()); + }//end service() + + public function testAnEmptyListMeansNobodyIsOnTheOfficeNetwork(): void { + $this->remote = '10.1.2.3'; + $this->assertFalse($this->service()->isOfficeRequest()); + }//end testAnEmptyListMeansNobodyIsOnTheOfficeNetwork() + + public function testAnIpv4RequestInsideARangeIsOnTheOfficeNetwork(): void { + $service = $this->service(); + $this->assertSame(['10.0.0.0/8', '192.168.12.0/24'], $service->saveRanges(raw: [' 10.0.0.0/8 ', '', '192.168.12.0/24', '10.0.0.0/8'])); + + $this->remote = '10.20.30.40'; + $this->assertTrue($service->isOfficeRequest()); + $this->remote = '192.168.13.1'; + $this->assertFalse($service->isOfficeRequest(), 'one subnet over is outside'); + $this->remote = '203.0.113.9'; + $this->assertFalse($service->isOfficeRequest()); + }//end testAnIpv4RequestInsideARangeIsOnTheOfficeNetwork() + + public function testAnIpv6RangeMatchesOnlyIpv6(): void { + $service = $this->service(); + $service->saveRanges(raw: ['2001:db8:12::/48']); + + $this->remote = '2001:db8:12:ff::1'; + $this->assertTrue($service->isOfficeRequest()); + $this->remote = '2001:db8:13::1'; + $this->assertFalse($service->isOfficeRequest()); + $this->remote = '10.0.0.1'; + $this->assertFalse($service->isOfficeRequest()); + }//end testAnIpv6RangeMatchesOnlyIpv6() + + public function testAMalformedRangeIsRefusedAndNothingStored(): void { + try { + $this->service()->saveRanges(raw: ['10.0.0.0/8', '10.0.0.0/33']); + $this->fail('a /33 was accepted'); + } catch (InvalidArgumentException) { + $this->assertSame([], $this->settings); + } + + $this->expectException(InvalidArgumentException::class); + $this->service()->saveRanges(raw: ['kantoor']); + }//end testAMalformedRangeIsRefusedAndNothingStored() + + public function testAnUnparsableRemoteAddressIsNotTheOfficeNetwork(): void { + $service = $this->service(); + $service->saveRanges(raw: ['0.0.0.0/0']); + $this->remote = 'not-an-ip'; + + $this->assertFalse($service->isOfficeRequest()); + }//end testAnUnparsableRemoteAddressIsNotTheOfficeNetwork() +}//end class diff --git a/tests/Unit/Service/TileInternalAddressTest.php b/tests/Unit/Service/TileInternalAddressTest.php new file mode 100644 index 000000000..3b88a9337 --- /dev/null +++ b/tests/Unit/Service/TileInternalAddressTest.php @@ -0,0 +1,105 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Service; + +use InvalidArgumentException; +use OCA\LaunchPad\Db\WidgetPlacement; +use OCA\LaunchPad\Db\WidgetPlacementMapper; +use OCA\LaunchPad\Service\PlacementService; +use OCA\LaunchPad\Service\PlacementUpdater; +use OCA\LaunchPad\Service\TileUpdater; +use PHPUnit\Framework\TestCase; + +class TileInternalAddressTest extends TestCase { + private int $updates = 0; + + private function service(): PlacementService { + $tile = new WidgetPlacement(); + $tile->setId(5); + $tile->setWidgetId('tile-abc'); + $tile->setTileLinkType('url'); + $tile->setTileLinkValue('https://zaken.gemeente.nl/'); + + $mapper = $this->createMock(WidgetPlacementMapper::class); + $mapper->method('find')->willReturn($tile); + $mapper->method('update')->willReturnCallback( + function (WidgetPlacement $placement): WidgetPlacement { + $this->updates++; + return $placement; + } + ); + + return new PlacementService( + placementMapper: $mapper, + tileUpdater: new TileUpdater(), + placementUpdater: new PlacementUpdater(), + ); + }//end service() + + /** + * @return array + */ + public static function validAddresses(): array { + return [ + 'intranet http' => ['http://zaken.intern.gemeente.local/'], + 'https' => ['https://10.1.2.3:8443/start'], + 'path on this Nextcloud' => ['/apps/files'], + 'cleared' => [''], + ]; + }//end validAddresses() + + /** + * @dataProvider validAddresses + */ + public function testAValidOfficeAddressIsStoredInTheContent(string $address): void { + $placement = $this->service()->updatePlacement(placementId: 5, data: ['content' => ['internalUrl' => $address, 'healthPingEnabled' => false]]); + + $this->assertSame($address, $placement->getContentArray()['internalUrl']); + $this->assertSame(1, $this->updates); + }//end testAValidOfficeAddressIsStoredInTheContent() + + /** + * @return array + */ + public static function invalidAddresses(): array { + return [ + 'script' => ['javascript:alert(1)'], + 'protocol relative' => ['//evil.example/'], + 'file' => ['file:///etc/passwd'], + 'no host' => ['https://'], + 'not text' => [['https://x.nl']], + ]; + }//end invalidAddresses() + + /** + * @dataProvider invalidAddresses + */ + public function testAnInvalidOfficeAddressRefusesTheUpdate(mixed $address): void { + try { + $this->service()->updatePlacement(placementId: 5, data: ['content' => ['internalUrl' => $address]]); + $this->fail('accepted an invalid office address'); + } catch (InvalidArgumentException) { + $this->assertSame(0, $this->updates, 'nothing may be written'); + } + }//end testAnInvalidOfficeAddressRefusesTheUpdate() +}//end class diff --git a/tests/Unit/Support/CidrIpFactory.php b/tests/Unit/Support/CidrIpFactory.php new file mode 100644 index 000000000..17ed6dac4 --- /dev/null +++ b/tests/Unit/Support/CidrIpFactory.php @@ -0,0 +1,119 @@ + + * @copyright 2026 Conduction b.v. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace Unit\Support; + +use InvalidArgumentException; +use OCP\Security\Ip\IAddress; +use OCP\Security\Ip\IFactory; +use OCP\Security\Ip\IRange; + +/** + * CIDR factory for tests. + */ +class CidrIpFactory implements IFactory { + /** + * Parse a range such as 10.0.0.0/8, 2001:db8::/32 or a single address. + * + * @param string $range The range. + * + * @return IRange + */ + public function rangeFromString(string $range): IRange { + [$ip, $bits] = array_pad(explode('/', $range, 2), 2, null); + $packed = @inet_pton((string)$ip); + $max = $packed === false ? 0 : strlen($packed) * 8; + if ($packed === false || ($bits !== null && (ctype_digit($bits) === false || (int)$bits > $max))) { + throw new InvalidArgumentException('Given range can’t be parsed'); + } + + $prefix = $bits === null ? $max : (int)$bits; + return new class($packed, $prefix, $range) implements IRange { + public function __construct(private string $packed, private int $prefix, private string $text) { + } + + public static function isValid(string $range): bool { + return true; + } + + public function contains(IAddress $address): bool { + $other = inet_pton((string)$address); + if ($other === false || strlen($other) !== strlen($this->packed)) { + return false; + } + + $bytes = intdiv($this->prefix, 8); + if (substr($other, 0, $bytes) !== substr($this->packed, 0, $bytes)) { + return false; + } + + $rest = $this->prefix % 8; + if ($rest === 0) { + return true; + } + + $mask = (0xFF << (8 - $rest)) & 0xFF; + return (ord($other[$bytes]) & $mask) === (ord($this->packed[$bytes]) & $mask); + } + + public function __toString(): string { + return $this->text; + } + }; + }//end rangeFromString() + + /** + * Parse an address. + * + * @param string $ip The address. + * + * @return IAddress + */ + public function addressFromString(string $ip): IAddress { + if (@inet_pton($ip) === false) { + throw new InvalidArgumentException('Given address can’t be parsed'); + } + + return new class($ip) implements IAddress { + public function __construct(private string $ip) { + } + + public static function isValid(string $ip): bool { + return true; + } + + public function matches(IRange ... $ranges): bool { + foreach ($ranges as $range) { + if ($range->contains($this) === true) { + return true; + } + } + + return false; + } + + public function __toString(): string { + return $this->ip; + } + }; + }//end addressFromString() +}//end class From ce989e48ff72a480d5684e1fb14a496737916296 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 30 Sep 2026 09:29:36 +0200 Subject: [PATCH 10/21] feat(tiles): a tile opens its office network address at the office, and administrators set the office networks The tile editor gets "Address on the office network" and says which address the author gets right now; the tile opens the internal address only when the server says the request comes from an office network. A new admin tab lists the ranges and names the administrator's own address. REQ-TIA-001..003 (launcher-tile-internal-address) --- l10n/be.js | 15 +- l10n/be.json | 15 +- l10n/bg.js | 15 +- l10n/bg.json | 15 +- l10n/bs.js | 15 +- l10n/bs.json | 15 +- l10n/ca.js | 15 +- l10n/ca.json | 15 +- l10n/cs.js | 15 +- l10n/cs.json | 15 +- l10n/da.js | 15 +- l10n/da.json | 15 +- l10n/de.js | 15 +- l10n/de.json | 15 +- l10n/el.js | 15 +- l10n/el.json | 15 +- l10n/en.js | 15 +- l10n/en.json | 15 +- l10n/es.js | 15 +- l10n/es.json | 15 +- l10n/et.js | 15 +- l10n/et.json | 15 +- l10n/fi.js | 15 +- l10n/fi.json | 15 +- l10n/fr.js | 15 +- l10n/fr.json | 15 +- l10n/ga.js | 15 +- l10n/ga.json | 15 +- l10n/hr.js | 15 +- l10n/hr.json | 15 +- l10n/hu.js | 15 +- l10n/hu.json | 15 +- l10n/is.js | 15 +- l10n/is.json | 15 +- l10n/it.js | 15 +- l10n/it.json | 15 +- l10n/lb.js | 15 +- l10n/lb.json | 15 +- l10n/lt.js | 15 +- l10n/lt.json | 15 +- l10n/lv.js | 15 +- l10n/lv.json | 15 +- l10n/mk.js | 15 +- l10n/mk.json | 15 +- l10n/mt.js | 15 +- l10n/mt.json | 15 +- l10n/nb.js | 15 +- l10n/nb.json | 15 +- l10n/nl.js | 15 +- l10n/nl.json | 15 +- l10n/pl.js | 15 +- l10n/pl.json | 15 +- l10n/pt.js | 15 +- l10n/pt.json | 15 +- l10n/rm.js | 15 +- l10n/rm.json | 15 +- l10n/ro.js | 15 +- l10n/ro.json | 15 +- l10n/ru.js | 15 +- l10n/ru.json | 15 +- l10n/sk.js | 15 +- l10n/sk.json | 15 +- l10n/sl.js | 15 +- l10n/sl.json | 15 +- l10n/sq.js | 15 +- l10n/sq.json | 15 +- l10n/sr.js | 15 +- l10n/sr.json | 15 +- l10n/sv.js | 15 +- l10n/sv.json | 15 +- l10n/tr.js | 15 +- l10n/tr.json | 15 +- l10n/uk.js | 15 +- l10n/uk.json | 15 +- lib/Controller/PageController.php | 2 +- lib/Service/InitialStateBuilder.php | 2 +- lib/Service/OfficeNetworkService.php | 2 +- src/components/TileWidget.vue | 48 ++++- src/components/WidgetRenderer.vue | 3 +- .../TileWidget.internalAddress.spec.js | 91 ++++++++++ src/components/admin/AdminSettings.vue | 9 + .../admin/tabs/OfficeNetworksTab.vue | 167 ++++++++++++++++++ .../tabs/__tests__/OfficeNetworksTab.spec.js | 94 ++++++++++ src/modals/TileEditor.vue | 42 +++++ src/utils/loadInitialState.js | 2 +- src/views/Views.vue | 13 +- 86 files changed, 1504 insertions(+), 81 deletions(-) create mode 100644 src/components/__tests__/TileWidget.internalAddress.spec.js create mode 100644 src/components/admin/tabs/OfficeNetworksTab.vue create mode 100644 src/components/admin/tabs/__tests__/OfficeNetworksTab.spec.js diff --git a/l10n/be.js b/l10n/be.js index be95dc318..0937d33c7 100644 --- a/l10n/be.js +++ b/l10n/be.js @@ -1350,6 +1350,19 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Гэты файл не ўдалося прачытаць як файл закладак.", "Room left on this dashboard: {count}. Pick fewer folders." : "Засталося месца на панэлі: {count}. Выберыце менш папак.", "The import failed; nothing was added." : "Імпарт не ўдаўся; нічога не дададзена.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прапушчана {count} закладка: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладкі: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладак: толькі вэб-адрасы могуць стаць пліткамі"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прапушчана {count} закладка: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладкі: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладак: толькі вэб-адрасы могуць стаць пліткамі"], + "Office networks" : "Офісныя сеткі", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Запыты з гэтых сетак адкрываюць офісны адрас пліткі, калі ён ёсць. Увядзіце па адным дыяпазоне IPv4 або IPv6 у радку, напрыклад 10.20.0.0/16.", + "Network ranges" : "Дыяпазоны сетак", + "Office networks saved." : "Офісныя сеткі захаваны.", + "Save office networks" : "Захаваць офісныя сеткі", + "Your current address {address} is on the office network." : "Ваш бягучы адрас {address} у офіснай сетцы.", + "Your current address {address} is not on the office network." : "Ваш бягучы адрас {address} не ў офіснай сетцы.", + "The office networks could not be loaded." : "Не ўдалося загрузіць офісныя сеткі.", + "The office networks could not be saved." : "Не ўдалося захаваць офісныя сеткі.", + "Address on the office network" : "Адрас у офіснай сетцы", + "Optional, for example http://zaken.intern" : "Неабавязкова, напрыклад http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Вы ў офіснай сетцы: гэтая плітка адкрывае ўнутраны адрас", + "You are not on the office network: this tile opens the main address" : "Вы не ў офіснай сетцы: гэтая плітка адкрывае асноўны адрас" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/be.json b/l10n/be.json index ea03a0987..97fe50383 100644 --- a/l10n/be.json +++ b/l10n/be.json @@ -1331,6 +1331,19 @@ "Прапушчана {count} закладка: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладкі: толькі вэб-адрасы могуць стаць пліткамі", "Прапушчана {count} закладак: толькі вэб-адрасы могуць стаць пліткамі" - ] + ], + "Office networks": "Офісныя сеткі", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Запыты з гэтых сетак адкрываюць офісны адрас пліткі, калі ён ёсць. Увядзіце па адным дыяпазоне IPv4 або IPv6 у радку, напрыклад 10.20.0.0/16.", + "Network ranges": "Дыяпазоны сетак", + "Office networks saved.": "Офісныя сеткі захаваны.", + "Save office networks": "Захаваць офісныя сеткі", + "Your current address {address} is on the office network.": "Ваш бягучы адрас {address} у офіснай сетцы.", + "Your current address {address} is not on the office network.": "Ваш бягучы адрас {address} не ў офіснай сетцы.", + "The office networks could not be loaded.": "Не ўдалося загрузіць офісныя сеткі.", + "The office networks could not be saved.": "Не ўдалося захаваць офісныя сеткі.", + "Address on the office network": "Адрас у офіснай сетцы", + "Optional, for example http://zaken.intern": "Неабавязкова, напрыклад http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Вы ў офіснай сетцы: гэтая плітка адкрывае ўнутраны адрас", + "You are not on the office network: this tile opens the main address": "Вы не ў офіснай сетцы: гэтая плітка адкрывае асноўны адрас" } } diff --git a/l10n/bg.js b/l10n/bg.js index 41a1b47d5..53172fc94 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Този файл не може да се прочете като файл с отметки.", "Room left on this dashboard: {count}. Pick fewer folders." : "Оставащо място на това табло: {count}. Изберете по-малко папки.", "The import failed; nothing was added." : "Импортирането не успя; нищо не е добавено.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} отметка е пропусната: само уеб адреси могат да станат плочки", "{count} отметки са пропуснати: само уеб адреси могат да станат плочки"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} отметка е пропусната: само уеб адреси могат да станат плочки", "{count} отметки са пропуснати: само уеб адреси могат да станат плочки"], + "Office networks" : "Офис мрежи", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Заявките от тези мрежи отварят офис адреса на плочката, ако има такъв. Въведете по един IPv4 или IPv6 диапазон на ред, например 10.20.0.0/16.", + "Network ranges" : "Мрежови диапазони", + "Office networks saved." : "Офис мрежите са запазени.", + "Save office networks" : "Запазване на офис мрежите", + "Your current address {address} is on the office network." : "Текущият ви адрес {address} е в офис мрежата.", + "Your current address {address} is not on the office network." : "Текущият ви адрес {address} не е в офис мрежата.", + "The office networks could not be loaded." : "Офис мрежите не можаха да се заредят.", + "The office networks could not be saved." : "Офис мрежите не можаха да се запазят.", + "Address on the office network" : "Адрес в офис мрежата", + "Optional, for example http://zaken.intern" : "По избор, например http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Вие сте в офис мрежата: тази плочка отваря вътрешния адрес", + "You are not on the office network: this tile opens the main address" : "Не сте в офис мрежата: тази плочка отваря основния адрес" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/bg.json b/l10n/bg.json index 019a22362..adfdb4886 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} отметка е пропусната: само уеб адреси могат да станат плочки", "{count} отметки са пропуснати: само уеб адреси могат да станат плочки" - ] + ], + "Office networks": "Офис мрежи", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Заявките от тези мрежи отварят офис адреса на плочката, ако има такъв. Въведете по един IPv4 или IPv6 диапазон на ред, например 10.20.0.0/16.", + "Network ranges": "Мрежови диапазони", + "Office networks saved.": "Офис мрежите са запазени.", + "Save office networks": "Запазване на офис мрежите", + "Your current address {address} is on the office network.": "Текущият ви адрес {address} е в офис мрежата.", + "Your current address {address} is not on the office network.": "Текущият ви адрес {address} не е в офис мрежата.", + "The office networks could not be loaded.": "Офис мрежите не можаха да се заредят.", + "The office networks could not be saved.": "Офис мрежите не можаха да се запазят.", + "Address on the office network": "Адрес в офис мрежата", + "Optional, for example http://zaken.intern": "По избор, например http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Вие сте в офис мрежата: тази плочка отваря вътрешния адрес", + "You are not on the office network: this tile opens the main address": "Не сте в офис мрежата: тази плочка отваря основния адрес" } } diff --git a/l10n/bs.js b/l10n/bs.js index e13237edb..c6fee38f3 100644 --- a/l10n/bs.js +++ b/l10n/bs.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ova datoteka se ne može pročitati kao datoteka oznaka.", "Room left on this dashboard: {count}. Pick fewer folders." : "Preostalo mjesta na ovoj ploči: {count}. Odaberite manje mapa.", "The import failed; nothing was added." : "Uvoz nije uspio; ništa nije dodano.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} oznaka: samo web adrese mogu postati pločice", "Preskočene {count} oznake: samo web adrese mogu postati pločice", "Preskočeno {count} oznaka: samo web adrese mogu postati pločice"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} oznaka: samo web adrese mogu postati pločice", "Preskočene {count} oznake: samo web adrese mogu postati pločice", "Preskočeno {count} oznaka: samo web adrese mogu postati pločice"], + "Office networks" : "Kancelarijske mreže", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Zahtjevi iz ovih mreža otvaraju kancelarijsku adresu pločice, ako je ima. Unesite jedan IPv4 ili IPv6 raspon po redu, npr. 10.20.0.0/16.", + "Network ranges" : "Mrežni rasponi", + "Office networks saved." : "Kancelarijske mreže su spremljene.", + "Save office networks" : "Spremi kancelarijske mreže", + "Your current address {address} is on the office network." : "Vaša trenutna adresa {address} je u kancelarijskoj mreži.", + "Your current address {address} is not on the office network." : "Vaša trenutna adresa {address} nije u kancelarijskoj mreži.", + "The office networks could not be loaded." : "Kancelarijske mreže nisu mogle biti učitane.", + "The office networks could not be saved." : "Kancelarijske mreže nisu mogle biti spremljene.", + "Address on the office network" : "Adresa u kancelarijskoj mreži", + "Optional, for example http://zaken.intern" : "Neobavezno, npr. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Vi ste u kancelarijskoj mreži: ova pločica otvara internu adresu", + "You are not on the office network: this tile opens the main address" : "Niste u kancelarijskoj mreži: ova pločica otvara glavnu adresu" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/bs.json b/l10n/bs.json index fb30adff1..73e7ca1e8 100644 --- a/l10n/bs.json +++ b/l10n/bs.json @@ -1331,6 +1331,19 @@ "Preskočena {count} oznaka: samo web adrese mogu postati pločice", "Preskočene {count} oznake: samo web adrese mogu postati pločice", "Preskočeno {count} oznaka: samo web adrese mogu postati pločice" - ] + ], + "Office networks": "Kancelarijske mreže", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Zahtjevi iz ovih mreža otvaraju kancelarijsku adresu pločice, ako je ima. Unesite jedan IPv4 ili IPv6 raspon po redu, npr. 10.20.0.0/16.", + "Network ranges": "Mrežni rasponi", + "Office networks saved.": "Kancelarijske mreže su spremljene.", + "Save office networks": "Spremi kancelarijske mreže", + "Your current address {address} is on the office network.": "Vaša trenutna adresa {address} je u kancelarijskoj mreži.", + "Your current address {address} is not on the office network.": "Vaša trenutna adresa {address} nije u kancelarijskoj mreži.", + "The office networks could not be loaded.": "Kancelarijske mreže nisu mogle biti učitane.", + "The office networks could not be saved.": "Kancelarijske mreže nisu mogle biti spremljene.", + "Address on the office network": "Adresa u kancelarijskoj mreži", + "Optional, for example http://zaken.intern": "Neobavezno, npr. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Vi ste u kancelarijskoj mreži: ova pločica otvara internu adresu", + "You are not on the office network: this tile opens the main address": "Niste u kancelarijskoj mreži: ova pločica otvara glavnu adresu" } } diff --git a/l10n/ca.js b/l10n/ca.js index 8954da06b..22ad7dc14 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Aquest fitxer no s'ha pogut llegir com a fitxer d'adreces d'interès.", "Room left on this dashboard: {count}. Pick fewer folders." : "Espai que queda en aquest tauler: {count}. Trieu menys carpetes.", "The import failed; nothing was added." : "La importació ha fallat; no s'ha afegit res.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["S'ha omès {count} adreça d'interès: només les adreces web poden ser rajoles", "S'han omès {count} adreces d'interès: només les adreces web poden ser rajoles"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["S'ha omès {count} adreça d'interès: només les adreces web poden ser rajoles", "S'han omès {count} adreces d'interès: només les adreces web poden ser rajoles"], + "Office networks" : "Xarxes de l'oficina", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Les peticions des d'aquestes xarxes obren l'adreça d'oficina de la rajola, si en té. Introduïu un interval IPv4 o IPv6 per línia, com ara 10.20.0.0/16.", + "Network ranges" : "Intervals de xarxa", + "Office networks saved." : "S'han desat les xarxes de l'oficina.", + "Save office networks" : "Desa les xarxes de l'oficina", + "Your current address {address} is on the office network." : "La vostra adreça actual {address} és a la xarxa de l'oficina.", + "Your current address {address} is not on the office network." : "La vostra adreça actual {address} no és a la xarxa de l'oficina.", + "The office networks could not be loaded." : "No s'han pogut carregar les xarxes de l'oficina.", + "The office networks could not be saved." : "No s'han pogut desar les xarxes de l'oficina.", + "Address on the office network" : "Adreça a la xarxa de l'oficina", + "Optional, for example http://zaken.intern" : "Opcional, per exemple http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Sou a la xarxa de l'oficina: aquesta rajola obre l'adreça interna", + "You are not on the office network: this tile opens the main address" : "No sou a la xarxa de l'oficina: aquesta rajola obre l'adreça principal" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/ca.json b/l10n/ca.json index f687b5978..aa8f84a7e 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "S'ha omès {count} adreça d'interès: només les adreces web poden ser rajoles", "S'han omès {count} adreces d'interès: només les adreces web poden ser rajoles" - ] + ], + "Office networks": "Xarxes de l'oficina", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Les peticions des d'aquestes xarxes obren l'adreça d'oficina de la rajola, si en té. Introduïu un interval IPv4 o IPv6 per línia, com ara 10.20.0.0/16.", + "Network ranges": "Intervals de xarxa", + "Office networks saved.": "S'han desat les xarxes de l'oficina.", + "Save office networks": "Desa les xarxes de l'oficina", + "Your current address {address} is on the office network.": "La vostra adreça actual {address} és a la xarxa de l'oficina.", + "Your current address {address} is not on the office network.": "La vostra adreça actual {address} no és a la xarxa de l'oficina.", + "The office networks could not be loaded.": "No s'han pogut carregar les xarxes de l'oficina.", + "The office networks could not be saved.": "No s'han pogut desar les xarxes de l'oficina.", + "Address on the office network": "Adreça a la xarxa de l'oficina", + "Optional, for example http://zaken.intern": "Opcional, per exemple http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Sou a la xarxa de l'oficina: aquesta rajola obre l'adreça interna", + "You are not on the office network: this tile opens the main address": "No sou a la xarxa de l'oficina: aquesta rajola obre l'adreça principal" } } diff --git a/l10n/cs.js b/l10n/cs.js index 9592240a6..e6594246b 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Tento soubor nelze přečíst jako soubor záložek.", "Room left on this dashboard: {count}. Pick fewer folders." : "Zbývající místo na této nástěnce: {count}. Vyberte méně složek.", "The import failed; nothing was added." : "Import se nezdařil; nic nebylo přidáno.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Přeskočena {count} záložka: dlaždicí může být jen webová adresa", "Přeskočeny {count} záložky: dlaždicí může být jen webová adresa", "Přeskočeno {count} záložek: dlaždicí může být jen webová adresa"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Přeskočena {count} záložka: dlaždicí může být jen webová adresa", "Přeskočeny {count} záložky: dlaždicí může být jen webová adresa", "Přeskočeno {count} záložek: dlaždicí může být jen webová adresa"], + "Office networks" : "Kancelářské sítě", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Požadavky z těchto sítí otevírají kancelářskou adresu dlaždice, pokud ji má. Zadejte jeden rozsah IPv4 nebo IPv6 na řádek, například 10.20.0.0/16.", + "Network ranges" : "Rozsahy sítí", + "Office networks saved." : "Kancelářské sítě jsou uloženy.", + "Save office networks" : "Uložit kancelářské sítě", + "Your current address {address} is on the office network." : "Vaše aktuální adresa {address} je v kancelářské síti.", + "Your current address {address} is not on the office network." : "Vaše aktuální adresa {address} není v kancelářské síti.", + "The office networks could not be loaded." : "Kancelářské sítě se nepodařilo načíst.", + "The office networks could not be saved." : "Kancelářské sítě se nepodařilo uložit.", + "Address on the office network" : "Adresa v kancelářské síti", + "Optional, for example http://zaken.intern" : "Nepovinné, například http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Jste v kancelářské síti: tato dlaždice otevře interní adresu", + "You are not on the office network: this tile opens the main address" : "Nejste v kancelářské síti: tato dlaždice otevře hlavní adresu" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/cs.json b/l10n/cs.json index d1360907f..b3bf2ee87 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -1331,6 +1331,19 @@ "Přeskočena {count} záložka: dlaždicí může být jen webová adresa", "Přeskočeny {count} záložky: dlaždicí může být jen webová adresa", "Přeskočeno {count} záložek: dlaždicí může být jen webová adresa" - ] + ], + "Office networks": "Kancelářské sítě", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Požadavky z těchto sítí otevírají kancelářskou adresu dlaždice, pokud ji má. Zadejte jeden rozsah IPv4 nebo IPv6 na řádek, například 10.20.0.0/16.", + "Network ranges": "Rozsahy sítí", + "Office networks saved.": "Kancelářské sítě jsou uloženy.", + "Save office networks": "Uložit kancelářské sítě", + "Your current address {address} is on the office network.": "Vaše aktuální adresa {address} je v kancelářské síti.", + "Your current address {address} is not on the office network.": "Vaše aktuální adresa {address} není v kancelářské síti.", + "The office networks could not be loaded.": "Kancelářské sítě se nepodařilo načíst.", + "The office networks could not be saved.": "Kancelářské sítě se nepodařilo uložit.", + "Address on the office network": "Adresa v kancelářské síti", + "Optional, for example http://zaken.intern": "Nepovinné, například http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Jste v kancelářské síti: tato dlaždice otevře interní adresu", + "You are not on the office network: this tile opens the main address": "Nejste v kancelářské síti: tato dlaždice otevře hlavní adresu" } } diff --git a/l10n/da.js b/l10n/da.js index 4ea308632..f66eba7e2 100644 --- a/l10n/da.js +++ b/l10n/da.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Filen kunne ikke læses som en bogmærkefil.", "Room left on this dashboard: {count}. Pick fewer folders." : "Plads tilbage på dette dashboard: {count}. Vælg færre mapper.", "The import failed; nothing was added." : "Importen mislykkedes; intet blev tilføjet.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bogmærke sprunget over: kun webadresser kan blive til felter", "{count} bogmærker sprunget over: kun webadresser kan blive til felter"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bogmærke sprunget over: kun webadresser kan blive til felter", "{count} bogmærker sprunget over: kun webadresser kan blive til felter"], + "Office networks" : "Kontornetværk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Forespørgsler fra disse netværk åbner feltets kontoradresse, hvis det har en. Angiv ét IPv4- eller IPv6-interval pr. linje, fx 10.20.0.0/16.", + "Network ranges" : "Netværksintervaller", + "Office networks saved." : "Kontornetværkene er gemt.", + "Save office networks" : "Gem kontornetværk", + "Your current address {address} is on the office network." : "Din nuværende adresse {address} er på kontornetværket.", + "Your current address {address} is not on the office network." : "Din nuværende adresse {address} er ikke på kontornetværket.", + "The office networks could not be loaded." : "Kontornetværkene kunne ikke indlæses.", + "The office networks could not be saved." : "Kontornetværkene kunne ikke gemmes.", + "Address on the office network" : "Adresse på kontornetværket", + "Optional, for example http://zaken.intern" : "Valgfrit, fx http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Du er på kontornetværket: dette felt åbner den interne adresse", + "You are not on the office network: this tile opens the main address" : "Du er ikke på kontornetværket: dette felt åbner hovedadressen" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/da.json b/l10n/da.json index fa9450a44..d4ba5f952 100644 --- a/l10n/da.json +++ b/l10n/da.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bogmærke sprunget over: kun webadresser kan blive til felter", "{count} bogmærker sprunget over: kun webadresser kan blive til felter" - ] + ], + "Office networks": "Kontornetværk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Forespørgsler fra disse netværk åbner feltets kontoradresse, hvis det har en. Angiv ét IPv4- eller IPv6-interval pr. linje, fx 10.20.0.0/16.", + "Network ranges": "Netværksintervaller", + "Office networks saved.": "Kontornetværkene er gemt.", + "Save office networks": "Gem kontornetværk", + "Your current address {address} is on the office network.": "Din nuværende adresse {address} er på kontornetværket.", + "Your current address {address} is not on the office network.": "Din nuværende adresse {address} er ikke på kontornetværket.", + "The office networks could not be loaded.": "Kontornetværkene kunne ikke indlæses.", + "The office networks could not be saved.": "Kontornetværkene kunne ikke gemmes.", + "Address on the office network": "Adresse på kontornetværket", + "Optional, for example http://zaken.intern": "Valgfrit, fx http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Du er på kontornetværket: dette felt åbner den interne adresse", + "You are not on the office network: this tile opens the main address": "Du er ikke på kontornetværket: dette felt åbner hovedadressen" } } diff --git a/l10n/de.js b/l10n/de.js index fb9f5b46f..b3e75f02b 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Diese Datei konnte nicht als Lesezeichendatei gelesen werden.", "Room left on this dashboard: {count}. Pick fewer folders." : "Platz auf diesem Dashboard: noch {count}. Wähle weniger Ordner.", "The import failed; nothing was added." : "Der Import ist fehlgeschlagen; nichts wurde hinzugefügt.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden", "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden", "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden"], + "Office networks" : "Büronetzwerke", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Anfragen aus diesen Netzwerken öffnen die Büroadresse einer Kachel, sofern sie eine hat. Gib einen IPv4- oder IPv6-Bereich pro Zeile ein, etwa 10.20.0.0/16.", + "Network ranges" : "Netzwerkbereiche", + "Office networks saved." : "Büronetzwerke gespeichert.", + "Save office networks" : "Büronetzwerke speichern", + "Your current address {address} is on the office network." : "Deine aktuelle Adresse {address} liegt im Büronetzwerk.", + "Your current address {address} is not on the office network." : "Deine aktuelle Adresse {address} liegt nicht im Büronetzwerk.", + "The office networks could not be loaded." : "Die Büronetzwerke konnten nicht geladen werden.", + "The office networks could not be saved." : "Die Büronetzwerke konnten nicht gespeichert werden.", + "Address on the office network" : "Adresse im Büronetzwerk", + "Optional, for example http://zaken.intern" : "Optional, zum Beispiel http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Du bist im Büronetzwerk: diese Kachel öffnet die interne Adresse", + "You are not on the office network: this tile opens the main address" : "Du bist nicht im Büronetzwerk: diese Kachel öffnet die Hauptadresse" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/de.json b/l10n/de.json index 41054c45e..3e857e00d 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden", "{count} Lesezeichen übersprungen: nur Webadressen können Kacheln werden" - ] + ], + "Office networks": "Büronetzwerke", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Anfragen aus diesen Netzwerken öffnen die Büroadresse einer Kachel, sofern sie eine hat. Gib einen IPv4- oder IPv6-Bereich pro Zeile ein, etwa 10.20.0.0/16.", + "Network ranges": "Netzwerkbereiche", + "Office networks saved.": "Büronetzwerke gespeichert.", + "Save office networks": "Büronetzwerke speichern", + "Your current address {address} is on the office network.": "Deine aktuelle Adresse {address} liegt im Büronetzwerk.", + "Your current address {address} is not on the office network.": "Deine aktuelle Adresse {address} liegt nicht im Büronetzwerk.", + "The office networks could not be loaded.": "Die Büronetzwerke konnten nicht geladen werden.", + "The office networks could not be saved.": "Die Büronetzwerke konnten nicht gespeichert werden.", + "Address on the office network": "Adresse im Büronetzwerk", + "Optional, for example http://zaken.intern": "Optional, zum Beispiel http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Du bist im Büronetzwerk: diese Kachel öffnet die interne Adresse", + "You are not on the office network: this tile opens the main address": "Du bist nicht im Büronetzwerk: diese Kachel öffnet die Hauptadresse" } } diff --git a/l10n/el.js b/l10n/el.js index 0959b9196..d3b179fbf 100644 --- a/l10n/el.js +++ b/l10n/el.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Αυτό το αρχείο δεν ήταν δυνατό να διαβαστεί ως αρχείο σελιδοδεικτών.", "Room left on this dashboard: {count}. Pick fewer folders." : "Διαθέσιμος χώρος σε αυτόν τον πίνακα: {count}. Επιλέξτε λιγότερους φακέλους.", "The import failed; nothing was added." : "Η εισαγωγή απέτυχε· δεν προστέθηκε τίποτα.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Παραλείφθηκε {count} σελιδοδείκτης: μόνο διευθύνσεις ιστού γίνονται πλακίδια", "Παραλείφθηκαν {count} σελιδοδείκτες: μόνο διευθύνσεις ιστού γίνονται πλακίδια"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Παραλείφθηκε {count} σελιδοδείκτης: μόνο διευθύνσεις ιστού γίνονται πλακίδια", "Παραλείφθηκαν {count} σελιδοδείκτες: μόνο διευθύνσεις ιστού γίνονται πλακίδια"], + "Office networks" : "Δίκτυα γραφείου", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Τα αιτήματα από αυτά τα δίκτυα ανοίγουν τη διεύθυνση γραφείου του πλακιδίου, αν έχει. Εισαγάγετε ένα εύρος IPv4 ή IPv6 ανά γραμμή, π.χ. 10.20.0.0/16.", + "Network ranges" : "Εύρη δικτύου", + "Office networks saved." : "Τα δίκτυα γραφείου αποθηκεύτηκαν.", + "Save office networks" : "Αποθήκευση δικτύων γραφείου", + "Your current address {address} is on the office network." : "Η τρέχουσα διεύθυνσή σας {address} είναι στο δίκτυο γραφείου.", + "Your current address {address} is not on the office network." : "Η τρέχουσα διεύθυνσή σας {address} δεν είναι στο δίκτυο γραφείου.", + "The office networks could not be loaded." : "Δεν ήταν δυνατή η φόρτωση των δικτύων γραφείου.", + "The office networks could not be saved." : "Δεν ήταν δυνατή η αποθήκευση των δικτύων γραφείου.", + "Address on the office network" : "Διεύθυνση στο δίκτυο γραφείου", + "Optional, for example http://zaken.intern" : "Προαιρετικό, π.χ. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Είστε στο δίκτυο γραφείου: αυτό το πλακίδιο ανοίγει την εσωτερική διεύθυνση", + "You are not on the office network: this tile opens the main address" : "Δεν είστε στο δίκτυο γραφείου: αυτό το πλακίδιο ανοίγει την κύρια διεύθυνση" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/el.json b/l10n/el.json index 50297f4e3..1d83adc42 100644 --- a/l10n/el.json +++ b/l10n/el.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "Παραλείφθηκε {count} σελιδοδείκτης: μόνο διευθύνσεις ιστού γίνονται πλακίδια", "Παραλείφθηκαν {count} σελιδοδείκτες: μόνο διευθύνσεις ιστού γίνονται πλακίδια" - ] + ], + "Office networks": "Δίκτυα γραφείου", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Τα αιτήματα από αυτά τα δίκτυα ανοίγουν τη διεύθυνση γραφείου του πλακιδίου, αν έχει. Εισαγάγετε ένα εύρος IPv4 ή IPv6 ανά γραμμή, π.χ. 10.20.0.0/16.", + "Network ranges": "Εύρη δικτύου", + "Office networks saved.": "Τα δίκτυα γραφείου αποθηκεύτηκαν.", + "Save office networks": "Αποθήκευση δικτύων γραφείου", + "Your current address {address} is on the office network.": "Η τρέχουσα διεύθυνσή σας {address} είναι στο δίκτυο γραφείου.", + "Your current address {address} is not on the office network.": "Η τρέχουσα διεύθυνσή σας {address} δεν είναι στο δίκτυο γραφείου.", + "The office networks could not be loaded.": "Δεν ήταν δυνατή η φόρτωση των δικτύων γραφείου.", + "The office networks could not be saved.": "Δεν ήταν δυνατή η αποθήκευση των δικτύων γραφείου.", + "Address on the office network": "Διεύθυνση στο δίκτυο γραφείου", + "Optional, for example http://zaken.intern": "Προαιρετικό, π.χ. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Είστε στο δίκτυο γραφείου: αυτό το πλακίδιο ανοίγει την εσωτερική διεύθυνση", + "You are not on the office network: this tile opens the main address": "Δεν είστε στο δίκτυο γραφείου: αυτό το πλακίδιο ανοίγει την κύρια διεύθυνση" } } diff --git a/l10n/en.js b/l10n/en.js index 6719f22cb..b516b4b8b 100644 --- a/l10n/en.js +++ b/l10n/en.js @@ -1339,7 +1339,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "This file could not be read as a bookmarks file.", "Room left on this dashboard: {count}. Pick fewer folders." : "Room left on this dashboard: {count}. Pick fewer folders.", "The import failed; nothing was added." : "The import failed; nothing was added.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark skipped: only web addresses can become tiles", "{count} bookmarks skipped: only web addresses can become tiles"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark skipped: only web addresses can become tiles", "{count} bookmarks skipped: only web addresses can become tiles"], + "Office networks" : "Office networks", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.", + "Network ranges" : "Network ranges", + "Office networks saved." : "Office networks saved.", + "Save office networks" : "Save office networks", + "Your current address {address} is on the office network." : "Your current address {address} is on the office network.", + "Your current address {address} is not on the office network." : "Your current address {address} is not on the office network.", + "The office networks could not be loaded." : "The office networks could not be loaded.", + "The office networks could not be saved." : "The office networks could not be saved.", + "Address on the office network" : "Address on the office network", + "Optional, for example http://zaken.intern" : "Optional, for example http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "You are on the office network: this tile opens the internal address", + "You are not on the office network: this tile opens the main address" : "You are not on the office network: this tile opens the main address" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/en.json b/l10n/en.json index b0e9488ab..bc19d3ebd 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -1307,6 +1307,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bookmark skipped: only web addresses can become tiles", "{count} bookmarks skipped: only web addresses can become tiles" - ] + ], + "Office networks": "Office networks", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.", + "Network ranges": "Network ranges", + "Office networks saved.": "Office networks saved.", + "Save office networks": "Save office networks", + "Your current address {address} is on the office network.": "Your current address {address} is on the office network.", + "Your current address {address} is not on the office network.": "Your current address {address} is not on the office network.", + "The office networks could not be loaded.": "The office networks could not be loaded.", + "The office networks could not be saved.": "The office networks could not be saved.", + "Address on the office network": "Address on the office network", + "Optional, for example http://zaken.intern": "Optional, for example http://zaken.intern", + "You are on the office network: this tile opens the internal address": "You are on the office network: this tile opens the internal address", + "You are not on the office network: this tile opens the main address": "You are not on the office network: this tile opens the main address" } } diff --git a/l10n/es.js b/l10n/es.js index 0f0b5e340..6cb0652bd 100644 --- a/l10n/es.js +++ b/l10n/es.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Este archivo no se pudo leer como archivo de marcadores.", "Room left on this dashboard: {count}. Pick fewer folders." : "Espacio que queda en este panel: {count}. Elige menos carpetas.", "The import failed; nothing was added." : "La importación falló; no se añadió nada.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Se omitió {count} marcador: solo las direcciones web pueden ser mosaicos", "Se omitieron {count} marcadores: solo las direcciones web pueden ser mosaicos"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Se omitió {count} marcador: solo las direcciones web pueden ser mosaicos", "Se omitieron {count} marcadores: solo las direcciones web pueden ser mosaicos"], + "Office networks" : "Redes de la oficina", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Las solicitudes desde estas redes abren la dirección de oficina del mosaico, si la tiene. Introduce un rango IPv4 o IPv6 por línea, como 10.20.0.0/16.", + "Network ranges" : "Rangos de red", + "Office networks saved." : "Redes de la oficina guardadas.", + "Save office networks" : "Guardar redes de la oficina", + "Your current address {address} is on the office network." : "Tu dirección actual {address} está en la red de la oficina.", + "Your current address {address} is not on the office network." : "Tu dirección actual {address} no está en la red de la oficina.", + "The office networks could not be loaded." : "No se pudieron cargar las redes de la oficina.", + "The office networks could not be saved." : "No se pudieron guardar las redes de la oficina.", + "Address on the office network" : "Dirección en la red de la oficina", + "Optional, for example http://zaken.intern" : "Opcional, por ejemplo http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Estás en la red de la oficina: este mosaico abre la dirección interna", + "You are not on the office network: this tile opens the main address" : "No estás en la red de la oficina: este mosaico abre la dirección principal" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/es.json b/l10n/es.json index 1d8b33ccc..668ed0605 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "Se omitió {count} marcador: solo las direcciones web pueden ser mosaicos", "Se omitieron {count} marcadores: solo las direcciones web pueden ser mosaicos" - ] + ], + "Office networks": "Redes de la oficina", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Las solicitudes desde estas redes abren la dirección de oficina del mosaico, si la tiene. Introduce un rango IPv4 o IPv6 por línea, como 10.20.0.0/16.", + "Network ranges": "Rangos de red", + "Office networks saved.": "Redes de la oficina guardadas.", + "Save office networks": "Guardar redes de la oficina", + "Your current address {address} is on the office network.": "Tu dirección actual {address} está en la red de la oficina.", + "Your current address {address} is not on the office network.": "Tu dirección actual {address} no está en la red de la oficina.", + "The office networks could not be loaded.": "No se pudieron cargar las redes de la oficina.", + "The office networks could not be saved.": "No se pudieron guardar las redes de la oficina.", + "Address on the office network": "Dirección en la red de la oficina", + "Optional, for example http://zaken.intern": "Opcional, por ejemplo http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Estás en la red de la oficina: este mosaico abre la dirección interna", + "You are not on the office network: this tile opens the main address": "No estás en la red de la oficina: este mosaico abre la dirección principal" } } diff --git a/l10n/et.js b/l10n/et.js index 1a42808b5..f7fd044d3 100644 --- a/l10n/et.js +++ b/l10n/et.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Seda faili ei õnnestunud lugeda järjehoidjate failina.", "Room left on this dashboard: {count}. Pick fewer folders." : "Selle töölaua vaba ruum: {count}. Vali vähem kaustu.", "The import failed; nothing was added." : "Import ebaõnnestus; midagi ei lisatud.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} järjehoidja jäeti vahele: paaniks saab olla ainult veebiaadress", "{count} järjehoidjat jäeti vahele: paaniks saab olla ainult veebiaadress"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} järjehoidja jäeti vahele: paaniks saab olla ainult veebiaadress", "{count} järjehoidjat jäeti vahele: paaniks saab olla ainult veebiaadress"], + "Office networks" : "Kontorivõrgud", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Nendest võrkudest tulevad päringud avavad paani kontoriaadressi, kui see on olemas. Sisesta üks IPv4- või IPv6-vahemik rea kohta, näiteks 10.20.0.0/16.", + "Network ranges" : "Võrguvahemikud", + "Office networks saved." : "Kontorivõrgud salvestatud.", + "Save office networks" : "Salvesta kontorivõrgud", + "Your current address {address} is on the office network." : "Sinu praegune aadress {address} on kontorivõrgus.", + "Your current address {address} is not on the office network." : "Sinu praegune aadress {address} ei ole kontorivõrgus.", + "The office networks could not be loaded." : "Kontorivõrke ei õnnestunud laadida.", + "The office networks could not be saved." : "Kontorivõrke ei õnnestunud salvestada.", + "Address on the office network" : "Aadress kontorivõrgus", + "Optional, for example http://zaken.intern" : "Valikuline, näiteks http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Oled kontorivõrgus: see paan avab sisemise aadressi", + "You are not on the office network: this tile opens the main address" : "Sa ei ole kontorivõrgus: see paan avab põhiaadressi" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/et.json b/l10n/et.json index c78794f71..e0adcf71b 100644 --- a/l10n/et.json +++ b/l10n/et.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} järjehoidja jäeti vahele: paaniks saab olla ainult veebiaadress", "{count} järjehoidjat jäeti vahele: paaniks saab olla ainult veebiaadress" - ] + ], + "Office networks": "Kontorivõrgud", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Nendest võrkudest tulevad päringud avavad paani kontoriaadressi, kui see on olemas. Sisesta üks IPv4- või IPv6-vahemik rea kohta, näiteks 10.20.0.0/16.", + "Network ranges": "Võrguvahemikud", + "Office networks saved.": "Kontorivõrgud salvestatud.", + "Save office networks": "Salvesta kontorivõrgud", + "Your current address {address} is on the office network.": "Sinu praegune aadress {address} on kontorivõrgus.", + "Your current address {address} is not on the office network.": "Sinu praegune aadress {address} ei ole kontorivõrgus.", + "The office networks could not be loaded.": "Kontorivõrke ei õnnestunud laadida.", + "The office networks could not be saved.": "Kontorivõrke ei õnnestunud salvestada.", + "Address on the office network": "Aadress kontorivõrgus", + "Optional, for example http://zaken.intern": "Valikuline, näiteks http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Oled kontorivõrgus: see paan avab sisemise aadressi", + "You are not on the office network: this tile opens the main address": "Sa ei ole kontorivõrgus: see paan avab põhiaadressi" } } diff --git a/l10n/fi.js b/l10n/fi.js index c1d45ff71..b0564bc76 100644 --- a/l10n/fi.js +++ b/l10n/fi.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Tiedostoa ei voitu lukea kirjanmerkkitiedostona.", "Room left on this dashboard: {count}. Pick fewer folders." : "Tilaa jäljellä tällä kojelaudalla: {count}. Valitse vähemmän kansioita.", "The import failed; nothing was added." : "Tuonti epäonnistui; mitään ei lisätty.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} kirjanmerkki ohitettiin: vain verkko-osoitteista voi tulla ruutuja", "{count} kirjanmerkkiä ohitettiin: vain verkko-osoitteista voi tulla ruutuja"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} kirjanmerkki ohitettiin: vain verkko-osoitteista voi tulla ruutuja", "{count} kirjanmerkkiä ohitettiin: vain verkko-osoitteista voi tulla ruutuja"], + "Office networks" : "Toimistoverkot", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Näistä verkoista tulevat pyynnöt avaavat ruudun toimisto-osoitteen, jos sellainen on. Anna yksi IPv4- tai IPv6-alue riviä kohden, esimerkiksi 10.20.0.0/16.", + "Network ranges" : "Verkkoalueet", + "Office networks saved." : "Toimistoverkot tallennettu.", + "Save office networks" : "Tallenna toimistoverkot", + "Your current address {address} is on the office network." : "Nykyinen osoitteesi {address} on toimistoverkossa.", + "Your current address {address} is not on the office network." : "Nykyinen osoitteesi {address} ei ole toimistoverkossa.", + "The office networks could not be loaded." : "Toimistoverkkoja ei voitu ladata.", + "The office networks could not be saved." : "Toimistoverkkoja ei voitu tallentaa.", + "Address on the office network" : "Osoite toimistoverkossa", + "Optional, for example http://zaken.intern" : "Valinnainen, esimerkiksi http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Olet toimistoverkossa: tämä ruutu avaa sisäisen osoitteen", + "You are not on the office network: this tile opens the main address" : "Et ole toimistoverkossa: tämä ruutu avaa pääosoitteen" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/fi.json b/l10n/fi.json index 59d8fb40f..a525d1a34 100644 --- a/l10n/fi.json +++ b/l10n/fi.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} kirjanmerkki ohitettiin: vain verkko-osoitteista voi tulla ruutuja", "{count} kirjanmerkkiä ohitettiin: vain verkko-osoitteista voi tulla ruutuja" - ] + ], + "Office networks": "Toimistoverkot", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Näistä verkoista tulevat pyynnöt avaavat ruudun toimisto-osoitteen, jos sellainen on. Anna yksi IPv4- tai IPv6-alue riviä kohden, esimerkiksi 10.20.0.0/16.", + "Network ranges": "Verkkoalueet", + "Office networks saved.": "Toimistoverkot tallennettu.", + "Save office networks": "Tallenna toimistoverkot", + "Your current address {address} is on the office network.": "Nykyinen osoitteesi {address} on toimistoverkossa.", + "Your current address {address} is not on the office network.": "Nykyinen osoitteesi {address} ei ole toimistoverkossa.", + "The office networks could not be loaded.": "Toimistoverkkoja ei voitu ladata.", + "The office networks could not be saved.": "Toimistoverkkoja ei voitu tallentaa.", + "Address on the office network": "Osoite toimistoverkossa", + "Optional, for example http://zaken.intern": "Valinnainen, esimerkiksi http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Olet toimistoverkossa: tämä ruutu avaa sisäisen osoitteen", + "You are not on the office network: this tile opens the main address": "Et ole toimistoverkossa: tämä ruutu avaa pääosoitteen" } } diff --git a/l10n/fr.js b/l10n/fr.js index 19897425e..b56f9050f 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ce fichier n'a pas pu être lu comme fichier de marque-pages.", "Room left on this dashboard: {count}. Pick fewer folders." : "Place restante sur ce tableau de bord : {count}. Choisissez moins de dossiers.", "The import failed; nothing was added." : "L'import a échoué ; rien n'a été ajouté.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marque-page ignoré : seules les adresses web peuvent devenir des tuiles", "{count} marque-pages ignorés : seules les adresses web peuvent devenir des tuiles"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marque-page ignoré : seules les adresses web peuvent devenir des tuiles", "{count} marque-pages ignorés : seules les adresses web peuvent devenir des tuiles"], + "Office networks" : "Réseaux du bureau", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Les requêtes venant de ces réseaux ouvrent l'adresse de bureau de la tuile, si elle en a une. Saisissez une plage IPv4 ou IPv6 par ligne, par exemple 10.20.0.0/16.", + "Network ranges" : "Plages réseau", + "Office networks saved." : "Réseaux du bureau enregistrés.", + "Save office networks" : "Enregistrer les réseaux du bureau", + "Your current address {address} is on the office network." : "Votre adresse actuelle {address} est sur le réseau du bureau.", + "Your current address {address} is not on the office network." : "Votre adresse actuelle {address} n'est pas sur le réseau du bureau.", + "The office networks could not be loaded." : "Les réseaux du bureau n'ont pas pu être chargés.", + "The office networks could not be saved." : "Les réseaux du bureau n'ont pas pu être enregistrés.", + "Address on the office network" : "Adresse sur le réseau du bureau", + "Optional, for example http://zaken.intern" : "Facultatif, par exemple http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Vous êtes sur le réseau du bureau : cette tuile ouvre l'adresse interne", + "You are not on the office network: this tile opens the main address" : "Vous n'êtes pas sur le réseau du bureau : cette tuile ouvre l'adresse principale" }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/fr.json b/l10n/fr.json index 2ede604cd..7c589e2dc 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} marque-page ignoré : seules les adresses web peuvent devenir des tuiles", "{count} marque-pages ignorés : seules les adresses web peuvent devenir des tuiles" - ] + ], + "Office networks": "Réseaux du bureau", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Les requêtes venant de ces réseaux ouvrent l'adresse de bureau de la tuile, si elle en a une. Saisissez une plage IPv4 ou IPv6 par ligne, par exemple 10.20.0.0/16.", + "Network ranges": "Plages réseau", + "Office networks saved.": "Réseaux du bureau enregistrés.", + "Save office networks": "Enregistrer les réseaux du bureau", + "Your current address {address} is on the office network.": "Votre adresse actuelle {address} est sur le réseau du bureau.", + "Your current address {address} is not on the office network.": "Votre adresse actuelle {address} n'est pas sur le réseau du bureau.", + "The office networks could not be loaded.": "Les réseaux du bureau n'ont pas pu être chargés.", + "The office networks could not be saved.": "Les réseaux du bureau n'ont pas pu être enregistrés.", + "Address on the office network": "Adresse sur le réseau du bureau", + "Optional, for example http://zaken.intern": "Facultatif, par exemple http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Vous êtes sur le réseau du bureau : cette tuile ouvre l'adresse interne", + "You are not on the office network: this tile opens the main address": "Vous n'êtes pas sur le réseau du bureau : cette tuile ouvre l'adresse principale" } } diff --git a/l10n/ga.js b/l10n/ga.js index c1852cdc1..de0f5e760 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Níorbh fhéidir an comhad seo a léamh mar chomhad leabharmharcanna.", "Room left on this dashboard: {count}. Pick fewer folders." : "Spás fágtha ar an deais seo: {count}. Roghnaigh níos lú fillteán.", "The import failed; nothing was added." : "Theip ar an iompórtáil; níor cuireadh aon rud leis.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna"], + "Office networks" : "Líonraí oifige", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Osclaíonn iarratais ó na líonraí seo seoladh oifige na tíle, má tá ceann aici. Iontráil raon IPv4 nó IPv6 amháin in aghaidh an líne, mar shampla 10.20.0.0/16.", + "Network ranges" : "Raonta líonra", + "Office networks saved." : "Líonraí oifige sábháilte.", + "Save office networks" : "Sábháil líonraí oifige", + "Your current address {address} is on the office network." : "Tá do sheoladh reatha {address} ar líonra na hoifige.", + "Your current address {address} is not on the office network." : "Níl do sheoladh reatha {address} ar líonra na hoifige.", + "The office networks could not be loaded." : "Níorbh fhéidir líonraí na hoifige a lódáil.", + "The office networks could not be saved." : "Níorbh fhéidir líonraí na hoifige a shábháil.", + "Address on the office network" : "Seoladh ar líonra na hoifige", + "Optional, for example http://zaken.intern" : "Roghnach, mar shampla http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Tá tú ar líonra na hoifige: osclaíonn an tíl seo an seoladh inmheánach", + "You are not on the office network: this tile opens the main address" : "Níl tú ar líonra na hoifige: osclaíonn an tíl seo an príomhsheoladh" }, "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);" ); diff --git a/l10n/ga.json b/l10n/ga.json index 88d579d3b..b8caa194a 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -1335,6 +1335,19 @@ "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna", "Scipeáladh {count} leabharmharc: ní féidir ach seoltaí gréasáin a dhéanamh ina dtíleanna" - ] + ], + "Office networks": "Líonraí oifige", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Osclaíonn iarratais ó na líonraí seo seoladh oifige na tíle, má tá ceann aici. Iontráil raon IPv4 nó IPv6 amháin in aghaidh an líne, mar shampla 10.20.0.0/16.", + "Network ranges": "Raonta líonra", + "Office networks saved.": "Líonraí oifige sábháilte.", + "Save office networks": "Sábháil líonraí oifige", + "Your current address {address} is on the office network.": "Tá do sheoladh reatha {address} ar líonra na hoifige.", + "Your current address {address} is not on the office network.": "Níl do sheoladh reatha {address} ar líonra na hoifige.", + "The office networks could not be loaded.": "Níorbh fhéidir líonraí na hoifige a lódáil.", + "The office networks could not be saved.": "Níorbh fhéidir líonraí na hoifige a shábháil.", + "Address on the office network": "Seoladh ar líonra na hoifige", + "Optional, for example http://zaken.intern": "Roghnach, mar shampla http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Tá tú ar líonra na hoifige: osclaíonn an tíl seo an seoladh inmheánach", + "You are not on the office network: this tile opens the main address": "Níl tú ar líonra na hoifige: osclaíonn an tíl seo an príomhsheoladh" } } diff --git a/l10n/hr.js b/l10n/hr.js index 76ec22e66..ef8f24489 100644 --- a/l10n/hr.js +++ b/l10n/hr.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ova datoteka ne može se pročitati kao datoteka knjižnih oznaka.", "Room left on this dashboard: {count}. Pick fewer folders." : "Preostalo mjesta na ovoj nadzornoj ploči: {count}. Odaberite manje mapa.", "The import failed; nothing was added." : "Uvoz nije uspio; ništa nije dodano.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} knjižna oznaka: samo web adrese mogu postati pločice", "Preskočene {count} knjižne oznake: samo web adrese mogu postati pločice", "Preskočeno {count} knjižnih oznaka: samo web adrese mogu postati pločice"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočena {count} knjižna oznaka: samo web adrese mogu postati pločice", "Preskočene {count} knjižne oznake: samo web adrese mogu postati pločice", "Preskočeno {count} knjižnih oznaka: samo web adrese mogu postati pločice"], + "Office networks" : "Uredske mreže", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Zahtjevi iz ovih mreža otvaraju uredsku adresu pločice, ako je ima. Unesite jedan IPv4 ili IPv6 raspon po retku, npr. 10.20.0.0/16.", + "Network ranges" : "Mrežni rasponi", + "Office networks saved." : "Uredske mreže su spremljene.", + "Save office networks" : "Spremi uredske mreže", + "Your current address {address} is on the office network." : "Vaša trenutačna adresa {address} je u uredskoj mreži.", + "Your current address {address} is not on the office network." : "Vaša trenutačna adresa {address} nije u uredskoj mreži.", + "The office networks could not be loaded." : "Uredske mreže nije bilo moguće učitati.", + "The office networks could not be saved." : "Uredske mreže nije bilo moguće spremiti.", + "Address on the office network" : "Adresa u uredskoj mreži", + "Optional, for example http://zaken.intern" : "Neobavezno, npr. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Vi ste u uredskoj mreži: ova pločica otvara internu adresu", + "You are not on the office network: this tile opens the main address" : "Niste u uredskoj mreži: ova pločica otvara glavnu adresu" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/hr.json b/l10n/hr.json index fdaaea45d..3f8a90a02 100644 --- a/l10n/hr.json +++ b/l10n/hr.json @@ -1331,6 +1331,19 @@ "Preskočena {count} knjižna oznaka: samo web adrese mogu postati pločice", "Preskočene {count} knjižne oznake: samo web adrese mogu postati pločice", "Preskočeno {count} knjižnih oznaka: samo web adrese mogu postati pločice" - ] + ], + "Office networks": "Uredske mreže", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Zahtjevi iz ovih mreža otvaraju uredsku adresu pločice, ako je ima. Unesite jedan IPv4 ili IPv6 raspon po retku, npr. 10.20.0.0/16.", + "Network ranges": "Mrežni rasponi", + "Office networks saved.": "Uredske mreže su spremljene.", + "Save office networks": "Spremi uredske mreže", + "Your current address {address} is on the office network.": "Vaša trenutačna adresa {address} je u uredskoj mreži.", + "Your current address {address} is not on the office network.": "Vaša trenutačna adresa {address} nije u uredskoj mreži.", + "The office networks could not be loaded.": "Uredske mreže nije bilo moguće učitati.", + "The office networks could not be saved.": "Uredske mreže nije bilo moguće spremiti.", + "Address on the office network": "Adresa u uredskoj mreži", + "Optional, for example http://zaken.intern": "Neobavezno, npr. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Vi ste u uredskoj mreži: ova pločica otvara internu adresu", + "You are not on the office network: this tile opens the main address": "Niste u uredskoj mreži: ova pločica otvara glavnu adresu" } } diff --git a/l10n/hu.js b/l10n/hu.js index cdfe8a9b4..2ca2123cf 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ezt a fájlt nem sikerült könyvjelzőfájlként beolvasni.", "Room left on this dashboard: {count}. Pick fewer folders." : "Szabad hely ezen az irányítópulton: {count}. Válassz kevesebb mappát.", "The import failed; nothing was added." : "Az importálás sikertelen; semmi sem került hozzáadásra.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} könyvjelző kimaradt: csak webcímekből lehet csempe", "{count} könyvjelző kimaradt: csak webcímekből lehet csempe"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} könyvjelző kimaradt: csak webcímekből lehet csempe", "{count} könyvjelző kimaradt: csak webcímekből lehet csempe"], + "Office networks" : "Irodai hálózatok", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Az ezekről a hálózatokról érkező kérések a csempe irodai címét nyitják meg, ha van ilyen. Soronként egy IPv4 vagy IPv6 tartományt adj meg, például 10.20.0.0/16.", + "Network ranges" : "Hálózati tartományok", + "Office networks saved." : "Irodai hálózatok mentve.", + "Save office networks" : "Irodai hálózatok mentése", + "Your current address {address} is on the office network." : "A jelenlegi címed ({address}) az irodai hálózaton van.", + "Your current address {address} is not on the office network." : "A jelenlegi címed ({address}) nincs az irodai hálózaton.", + "The office networks could not be loaded." : "Az irodai hálózatokat nem sikerült betölteni.", + "The office networks could not be saved." : "Az irodai hálózatokat nem sikerült menteni.", + "Address on the office network" : "Cím az irodai hálózaton", + "Optional, for example http://zaken.intern" : "Nem kötelező, például http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Az irodai hálózaton vagy: ez a csempe a belső címet nyitja meg", + "You are not on the office network: this tile opens the main address" : "Nem az irodai hálózaton vagy: ez a csempe a fő címet nyitja meg" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/hu.json b/l10n/hu.json index 075f7ce4e..ae521931f 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} könyvjelző kimaradt: csak webcímekből lehet csempe", "{count} könyvjelző kimaradt: csak webcímekből lehet csempe" - ] + ], + "Office networks": "Irodai hálózatok", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Az ezekről a hálózatokról érkező kérések a csempe irodai címét nyitják meg, ha van ilyen. Soronként egy IPv4 vagy IPv6 tartományt adj meg, például 10.20.0.0/16.", + "Network ranges": "Hálózati tartományok", + "Office networks saved.": "Irodai hálózatok mentve.", + "Save office networks": "Irodai hálózatok mentése", + "Your current address {address} is on the office network.": "A jelenlegi címed ({address}) az irodai hálózaton van.", + "Your current address {address} is not on the office network.": "A jelenlegi címed ({address}) nincs az irodai hálózaton.", + "The office networks could not be loaded.": "Az irodai hálózatokat nem sikerült betölteni.", + "The office networks could not be saved.": "Az irodai hálózatokat nem sikerült menteni.", + "Address on the office network": "Cím az irodai hálózaton", + "Optional, for example http://zaken.intern": "Nem kötelező, például http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Az irodai hálózaton vagy: ez a csempe a belső címet nyitja meg", + "You are not on the office network: this tile opens the main address": "Nem az irodai hálózaton vagy: ez a csempe a fő címet nyitja meg" } } diff --git a/l10n/is.js b/l10n/is.js index 31a01c1d2..6daf99242 100644 --- a/l10n/is.js +++ b/l10n/is.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ekki tókst að lesa þessa skrá sem bókamerkjaskrá.", "Room left on this dashboard: {count}. Pick fewer folders." : "Pláss eftir á þessu stjórnborði: {count}. Veldu færri möppur.", "The import failed; nothing was added." : "Innflutningur mistókst; engu var bætt við.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bókamerki sleppt: aðeins vefslóðir geta orðið reitir", "{count} bókamerkjum sleppt: aðeins vefslóðir geta orðið reitir"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bókamerki sleppt: aðeins vefslóðir geta orðið reitir", "{count} bókamerkjum sleppt: aðeins vefslóðir geta orðið reitir"], + "Office networks" : "Skrifstofunet", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Beiðnir frá þessum netum opna skrifstofuslóð reitsins, ef hún er til. Sláðu inn eitt IPv4- eða IPv6-svið í hverja línu, t.d. 10.20.0.0/16.", + "Network ranges" : "Netsvið", + "Office networks saved." : "Skrifstofunet vistuð.", + "Save office networks" : "Vista skrifstofunet", + "Your current address {address} is on the office network." : "Núverandi vistfang þitt {address} er á skrifstofunetinu.", + "Your current address {address} is not on the office network." : "Núverandi vistfang þitt {address} er ekki á skrifstofunetinu.", + "The office networks could not be loaded." : "Ekki tókst að hlaða inn skrifstofunetum.", + "The office networks could not be saved." : "Ekki tókst að vista skrifstofunetin.", + "Address on the office network" : "Slóð á skrifstofunetinu", + "Optional, for example http://zaken.intern" : "Valfrjálst, t.d. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Þú ert á skrifstofunetinu: þessi reitur opnar innri slóðina", + "You are not on the office network: this tile opens the main address" : "Þú ert ekki á skrifstofunetinu: þessi reitur opnar aðalslóðina" }, "nplurals=2; plural=(n%10!=1 || n%100==11);" ); diff --git a/l10n/is.json b/l10n/is.json index 5e309bbdd..794f0f794 100644 --- a/l10n/is.json +++ b/l10n/is.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bókamerki sleppt: aðeins vefslóðir geta orðið reitir", "{count} bókamerkjum sleppt: aðeins vefslóðir geta orðið reitir" - ] + ], + "Office networks": "Skrifstofunet", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Beiðnir frá þessum netum opna skrifstofuslóð reitsins, ef hún er til. Sláðu inn eitt IPv4- eða IPv6-svið í hverja línu, t.d. 10.20.0.0/16.", + "Network ranges": "Netsvið", + "Office networks saved.": "Skrifstofunet vistuð.", + "Save office networks": "Vista skrifstofunet", + "Your current address {address} is on the office network.": "Núverandi vistfang þitt {address} er á skrifstofunetinu.", + "Your current address {address} is not on the office network.": "Núverandi vistfang þitt {address} er ekki á skrifstofunetinu.", + "The office networks could not be loaded.": "Ekki tókst að hlaða inn skrifstofunetum.", + "The office networks could not be saved.": "Ekki tókst að vista skrifstofunetin.", + "Address on the office network": "Slóð á skrifstofunetinu", + "Optional, for example http://zaken.intern": "Valfrjálst, t.d. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Þú ert á skrifstofunetinu: þessi reitur opnar innri slóðina", + "You are not on the office network: this tile opens the main address": "Þú ert ekki á skrifstofunetinu: þessi reitur opnar aðalslóðina" } } diff --git a/l10n/it.js b/l10n/it.js index acb6ec296..12a195aa5 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Questo file non può essere letto come file di segnalibri.", "Room left on this dashboard: {count}. Pick fewer folders." : "Spazio rimasto su questa dashboard: {count}. Scegli meno cartelle.", "The import failed; nothing was added." : "Importazione non riuscita; non è stato aggiunto nulla.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnalibro saltato: solo gli indirizzi web possono diventare riquadri", "{count} segnalibri saltati: solo gli indirizzi web possono diventare riquadri"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnalibro saltato: solo gli indirizzi web possono diventare riquadri", "{count} segnalibri saltati: solo gli indirizzi web possono diventare riquadri"], + "Office networks" : "Reti dell'ufficio", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Le richieste da queste reti aprono l'indirizzo d'ufficio del riquadro, se ne ha uno. Inserisci un intervallo IPv4 o IPv6 per riga, ad esempio 10.20.0.0/16.", + "Network ranges" : "Intervalli di rete", + "Office networks saved." : "Reti dell'ufficio salvate.", + "Save office networks" : "Salva reti dell'ufficio", + "Your current address {address} is on the office network." : "Il tuo indirizzo attuale {address} è nella rete dell'ufficio.", + "Your current address {address} is not on the office network." : "Il tuo indirizzo attuale {address} non è nella rete dell'ufficio.", + "The office networks could not be loaded." : "Non è stato possibile caricare le reti dell'ufficio.", + "The office networks could not be saved." : "Non è stato possibile salvare le reti dell'ufficio.", + "Address on the office network" : "Indirizzo nella rete dell'ufficio", + "Optional, for example http://zaken.intern" : "Facoltativo, ad esempio http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Sei nella rete dell'ufficio: questo riquadro apre l'indirizzo interno", + "You are not on the office network: this tile opens the main address" : "Non sei nella rete dell'ufficio: questo riquadro apre l'indirizzo principale" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/it.json b/l10n/it.json index c3b0da51c..815dd18b0 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} segnalibro saltato: solo gli indirizzi web possono diventare riquadri", "{count} segnalibri saltati: solo gli indirizzi web possono diventare riquadri" - ] + ], + "Office networks": "Reti dell'ufficio", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Le richieste da queste reti aprono l'indirizzo d'ufficio del riquadro, se ne ha uno. Inserisci un intervallo IPv4 o IPv6 per riga, ad esempio 10.20.0.0/16.", + "Network ranges": "Intervalli di rete", + "Office networks saved.": "Reti dell'ufficio salvate.", + "Save office networks": "Salva reti dell'ufficio", + "Your current address {address} is on the office network.": "Il tuo indirizzo attuale {address} è nella rete dell'ufficio.", + "Your current address {address} is not on the office network.": "Il tuo indirizzo attuale {address} non è nella rete dell'ufficio.", + "The office networks could not be loaded.": "Non è stato possibile caricare le reti dell'ufficio.", + "The office networks could not be saved.": "Non è stato possibile salvare le reti dell'ufficio.", + "Address on the office network": "Indirizzo nella rete dell'ufficio", + "Optional, for example http://zaken.intern": "Facoltativo, ad esempio http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Sei nella rete dell'ufficio: questo riquadro apre l'indirizzo interno", + "You are not on the office network: this tile opens the main address": "Non sei nella rete dell'ufficio: questo riquadro apre l'indirizzo principale" } } diff --git a/l10n/lb.js b/l10n/lb.js index 8c679fb93..7110d9940 100644 --- a/l10n/lb.js +++ b/l10n/lb.js @@ -1350,7 +1350,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Dës Datei konnt net als Lieszeechendatei gelies ginn.", "Room left on this dashboard: {count}. Pick fewer folders." : "Plaz op dësem Dashboard: nach {count}. Wiel manner Dossieren.", "The import failed; nothing was added." : "Den Import ass feelgeschloen; näischt gouf derbäigesat.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn", "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn", "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn"], + "Office networks" : "Büronetzwierker", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Ufroe vun dësen Netzwierker maachen d'Büroadress vun enger Kachel op, wann se eng huet. Gëff eng IPv4- oder IPv6-Plage pro Linn an, wéi 10.20.0.0/16.", + "Network ranges" : "Netzwierkplagen", + "Office networks saved." : "Büronetzwierker gespäichert.", + "Save office networks" : "Büronetzwierker späicheren", + "Your current address {address} is on the office network." : "Deng aktuell Adress {address} ass am Büronetzwierk.", + "Your current address {address} is not on the office network." : "Deng aktuell Adress {address} ass net am Büronetzwierk.", + "The office networks could not be loaded." : "D'Büronetzwierker konnten net geluede ginn.", + "The office networks could not be saved." : "D'Büronetzwierker konnten net gespäichert ginn.", + "Address on the office network" : "Adress am Büronetzwierk", + "Optional, for example http://zaken.intern" : "Optional, zum Beispill http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Du bass am Büronetzwierk: dës Kachel mécht déi intern Adress op", + "You are not on the office network: this tile opens the main address" : "Du bass net am Büronetzwierk: dës Kachel mécht d'Haaptadress op" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/lb.json b/l10n/lb.json index 4b599e1cd..735598109 100644 --- a/l10n/lb.json +++ b/l10n/lb.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn", "{count} Lieszeechen iwwersprongen: nëmme Webadresse kënne Kachele ginn" - ] + ], + "Office networks": "Büronetzwierker", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Ufroe vun dësen Netzwierker maachen d'Büroadress vun enger Kachel op, wann se eng huet. Gëff eng IPv4- oder IPv6-Plage pro Linn an, wéi 10.20.0.0/16.", + "Network ranges": "Netzwierkplagen", + "Office networks saved.": "Büronetzwierker gespäichert.", + "Save office networks": "Büronetzwierker späicheren", + "Your current address {address} is on the office network.": "Deng aktuell Adress {address} ass am Büronetzwierk.", + "Your current address {address} is not on the office network.": "Deng aktuell Adress {address} ass net am Büronetzwierk.", + "The office networks could not be loaded.": "D'Büronetzwierker konnten net geluede ginn.", + "The office networks could not be saved.": "D'Büronetzwierker konnten net gespäichert ginn.", + "Address on the office network": "Adress am Büronetzwierk", + "Optional, for example http://zaken.intern": "Optional, zum Beispill http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Du bass am Büronetzwierk: dës Kachel mécht déi intern Adress op", + "You are not on the office network: this tile opens the main address": "Du bass net am Büronetzwierk: dës Kachel mécht d'Haaptadress op" } } diff --git a/l10n/lt.js b/l10n/lt.js index 90570facd..b36e40b23 100644 --- a/l10n/lt.js +++ b/l10n/lt.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Šio failo nepavyko perskaityti kaip žymių failo.", "Room left on this dashboard: {count}. Pick fewer folders." : "Laisvos vietos šiame skydelyje: {count}. Pasirinkite mažiau aplankų.", "The import failed; nothing was added." : "Importuoti nepavyko; nieko nepridėta.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Praleista {count} žymė: plytele gali tapti tik žiniatinklio adresas", "Praleistos {count} žymės: plytele gali tapti tik žiniatinklio adresas", "Praleista {count} žymių: plytele gali tapti tik žiniatinklio adresas"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Praleista {count} žymė: plytele gali tapti tik žiniatinklio adresas", "Praleistos {count} žymės: plytele gali tapti tik žiniatinklio adresas", "Praleista {count} žymių: plytele gali tapti tik žiniatinklio adresas"], + "Office networks" : "Biuro tinklai", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Užklausos iš šių tinklų atveria plytelės biuro adresą, jei jis yra. Įveskite po vieną IPv4 arba IPv6 diapazoną eilutėje, pvz., 10.20.0.0/16.", + "Network ranges" : "Tinklo diapazonai", + "Office networks saved." : "Biuro tinklai išsaugoti.", + "Save office networks" : "Išsaugoti biuro tinklus", + "Your current address {address} is on the office network." : "Jūsų dabartinis adresas {address} yra biuro tinkle.", + "Your current address {address} is not on the office network." : "Jūsų dabartinis adresas {address} nėra biuro tinkle.", + "The office networks could not be loaded." : "Nepavyko įkelti biuro tinklų.", + "The office networks could not be saved." : "Nepavyko išsaugoti biuro tinklų.", + "Address on the office network" : "Adresas biuro tinkle", + "Optional, for example http://zaken.intern" : "Neprivaloma, pvz., http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Esate biuro tinkle: ši plytelė atveria vidinį adresą", + "You are not on the office network: this tile opens the main address" : "Nesate biuro tinkle: ši plytelė atveria pagrindinį adresą" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/lt.json b/l10n/lt.json index 7fb79f50b..d992957a8 100644 --- a/l10n/lt.json +++ b/l10n/lt.json @@ -1331,6 +1331,19 @@ "Praleista {count} žymė: plytele gali tapti tik žiniatinklio adresas", "Praleistos {count} žymės: plytele gali tapti tik žiniatinklio adresas", "Praleista {count} žymių: plytele gali tapti tik žiniatinklio adresas" - ] + ], + "Office networks": "Biuro tinklai", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Užklausos iš šių tinklų atveria plytelės biuro adresą, jei jis yra. Įveskite po vieną IPv4 arba IPv6 diapazoną eilutėje, pvz., 10.20.0.0/16.", + "Network ranges": "Tinklo diapazonai", + "Office networks saved.": "Biuro tinklai išsaugoti.", + "Save office networks": "Išsaugoti biuro tinklus", + "Your current address {address} is on the office network.": "Jūsų dabartinis adresas {address} yra biuro tinkle.", + "Your current address {address} is not on the office network.": "Jūsų dabartinis adresas {address} nėra biuro tinkle.", + "The office networks could not be loaded.": "Nepavyko įkelti biuro tinklų.", + "The office networks could not be saved.": "Nepavyko išsaugoti biuro tinklų.", + "Address on the office network": "Adresas biuro tinkle", + "Optional, for example http://zaken.intern": "Neprivaloma, pvz., http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Esate biuro tinkle: ši plytelė atveria vidinį adresą", + "You are not on the office network: this tile opens the main address": "Nesate biuro tinkle: ši plytelė atveria pagrindinį adresą" } } diff --git a/l10n/lv.js b/l10n/lv.js index 17bfa781e..1a1e211cf 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Šo failu neizdevās nolasīt kā grāmatzīmju failu.", "Room left on this dashboard: {count}. Pick fewer folders." : "Brīvā vieta šajā panelī: {count}. Izvēlieties mazāk mapju.", "The import failed; nothing was added." : "Importēšana neizdevās; nekas netika pievienots.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaistas {count} grāmatzīmes: par flīzi var kļūt tikai tīmekļa adrese"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaistas {count} grāmatzīmes: par flīzi var kļūt tikai tīmekļa adrese"], + "Office networks" : "Biroja tīkli", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Pieprasījumi no šiem tīkliem atver flīzes biroja adresi, ja tāda ir. Ievadiet vienu IPv4 vai IPv6 diapazonu rindā, piemēram, 10.20.0.0/16.", + "Network ranges" : "Tīkla diapazoni", + "Office networks saved." : "Biroja tīkli saglabāti.", + "Save office networks" : "Saglabāt biroja tīklus", + "Your current address {address} is on the office network." : "Jūsu pašreizējā adrese {address} ir biroja tīklā.", + "Your current address {address} is not on the office network." : "Jūsu pašreizējā adrese {address} nav biroja tīklā.", + "The office networks could not be loaded." : "Neizdevās ielādēt biroja tīklus.", + "The office networks could not be saved." : "Neizdevās saglabāt biroja tīklus.", + "Address on the office network" : "Adrese biroja tīklā", + "Optional, for example http://zaken.intern" : "Nav obligāti, piemēram, http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Jūs esat biroja tīklā: šī flīze atver iekšējo adresi", + "You are not on the office network: this tile opens the main address" : "Jūs neesat biroja tīklā: šī flīze atver galveno adresi" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" ); diff --git a/l10n/lv.json b/l10n/lv.json index 6a8cbbf36..aa6113474 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -1331,6 +1331,19 @@ "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaista {count} grāmatzīme: par flīzi var kļūt tikai tīmekļa adrese", "Izlaistas {count} grāmatzīmes: par flīzi var kļūt tikai tīmekļa adrese" - ] + ], + "Office networks": "Biroja tīkli", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Pieprasījumi no šiem tīkliem atver flīzes biroja adresi, ja tāda ir. Ievadiet vienu IPv4 vai IPv6 diapazonu rindā, piemēram, 10.20.0.0/16.", + "Network ranges": "Tīkla diapazoni", + "Office networks saved.": "Biroja tīkli saglabāti.", + "Save office networks": "Saglabāt biroja tīklus", + "Your current address {address} is on the office network.": "Jūsu pašreizējā adrese {address} ir biroja tīklā.", + "Your current address {address} is not on the office network.": "Jūsu pašreizējā adrese {address} nav biroja tīklā.", + "The office networks could not be loaded.": "Neizdevās ielādēt biroja tīklus.", + "The office networks could not be saved.": "Neizdevās saglabāt biroja tīklus.", + "Address on the office network": "Adrese biroja tīklā", + "Optional, for example http://zaken.intern": "Nav obligāti, piemēram, http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Jūs esat biroja tīklā: šī flīze atver iekšējo adresi", + "You are not on the office network: this tile opens the main address": "Jūs neesat biroja tīklā: šī flīze atver galveno adresi" } } diff --git a/l10n/mk.js b/l10n/mk.js index 5a9bab9ab..428f42da2 100644 --- a/l10n/mk.js +++ b/l10n/mk.js @@ -1350,7 +1350,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Оваа датотека не може да се прочита како датотека со обележувачи.", "Room left on this dashboard: {count}. Pick fewer folders." : "Преостанато место на оваа табла: {count}. Изберете помалку папки.", "The import failed; nothing was added." : "Увезувањето не успеа; ништо не е додадено.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескокнат {count} обележувач: само веб-адреси можат да станат плочки", "Прескокнати {count} обележувачи: само веб-адреси можат да станат плочки"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескокнат {count} обележувач: само веб-адреси можат да станат плочки", "Прескокнати {count} обележувачи: само веб-адреси можат да станат плочки"], + "Office networks" : "Канцелариски мрежи", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Барањата од овие мрежи ја отвораат канцелариската адреса на плочката, ако ја има. Внесете по еден IPv4 или IPv6 опсег во ред, на пр. 10.20.0.0/16.", + "Network ranges" : "Мрежни опсези", + "Office networks saved." : "Канцелариските мрежи се зачувани.", + "Save office networks" : "Зачувај канцелариски мрежи", + "Your current address {address} is on the office network." : "Вашата моментална адреса {address} е на канцелариската мрежа.", + "Your current address {address} is not on the office network." : "Вашата моментална адреса {address} не е на канцелариската мрежа.", + "The office networks could not be loaded." : "Канцелариските мрежи не можеа да се вчитаат.", + "The office networks could not be saved." : "Канцелариските мрежи не можеа да се зачуваат.", + "Address on the office network" : "Адреса на канцелариската мрежа", + "Optional, for example http://zaken.intern" : "Незадолжително, на пр. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Вие сте на канцелариската мрежа: оваа плочка ја отвора внатрешната адреса", + "You are not on the office network: this tile opens the main address" : "Не сте на канцелариската мрежа: оваа плочка ја отвора главната адреса" }, "nplurals=2; plural=(n%10==1 && n%100!=11 ? 0 : 1);" ); diff --git a/l10n/mk.json b/l10n/mk.json index f01e1051c..5079e3786 100644 --- a/l10n/mk.json +++ b/l10n/mk.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "Прескокнат {count} обележувач: само веб-адреси можат да станат плочки", "Прескокнати {count} обележувачи: само веб-адреси можат да станат плочки" - ] + ], + "Office networks": "Канцелариски мрежи", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Барањата од овие мрежи ја отвораат канцелариската адреса на плочката, ако ја има. Внесете по еден IPv4 или IPv6 опсег во ред, на пр. 10.20.0.0/16.", + "Network ranges": "Мрежни опсези", + "Office networks saved.": "Канцелариските мрежи се зачувани.", + "Save office networks": "Зачувај канцелариски мрежи", + "Your current address {address} is on the office network.": "Вашата моментална адреса {address} е на канцелариската мрежа.", + "Your current address {address} is not on the office network.": "Вашата моментална адреса {address} не е на канцелариската мрежа.", + "The office networks could not be loaded.": "Канцелариските мрежи не можеа да се вчитаат.", + "The office networks could not be saved.": "Канцелариските мрежи не можеа да се зачуваат.", + "Address on the office network": "Адреса на канцелариската мрежа", + "Optional, for example http://zaken.intern": "Незадолжително, на пр. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Вие сте на канцелариската мрежа: оваа плочка ја отвора внатрешната адреса", + "You are not on the office network: this tile opens the main address": "Не сте на канцелариската мрежа: оваа плочка ја отвора главната адреса" } } diff --git a/l10n/mt.js b/l10n/mt.js index b1aafa4d6..0df63d365 100644 --- a/l10n/mt.js +++ b/l10n/mt.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Dan il-fajl ma setax jinqara bħala fajl tal-bookmarks.", "Room left on this dashboard: {count}. Pick fewer folders." : "Spazju li fadal f'dan id-dashboard: {count}. Agħżel inqas folders.", "The import failed; nothing was added." : "L-importazzjoni falliet; ma żdied xejn.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum"], + "Office networks" : "Netwerks tal-uffiċċju", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Talbiet minn dawn in-netwerks jiftħu l-indirizz tal-uffiċċju tal-madum, jekk għandu wieħed. Daħħal firxa IPv4 jew IPv6 waħda f'kull linja, bħal 10.20.0.0/16.", + "Network ranges" : "Firxiet tan-netwerk", + "Office networks saved." : "In-netwerks tal-uffiċċju ġew salvati.", + "Save office networks" : "Issejvja n-netwerks tal-uffiċċju", + "Your current address {address} is on the office network." : "L-indirizz attwali tiegħek {address} jinsab fuq in-netwerk tal-uffiċċju.", + "Your current address {address} is not on the office network." : "L-indirizz attwali tiegħek {address} mhuwiex fuq in-netwerk tal-uffiċċju.", + "The office networks could not be loaded." : "In-netwerks tal-uffiċċju ma setgħux jitgħabbew.", + "The office networks could not be saved." : "In-netwerks tal-uffiċċju ma setgħux jiġu salvati.", + "Address on the office network" : "Indirizz fuq in-netwerk tal-uffiċċju", + "Optional, for example http://zaken.intern" : "Mhux obbligatorju, pereżempju http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Int fuq in-netwerk tal-uffiċċju: dan il-madum jiftaħ l-indirizz intern", + "You are not on the office network: this tile opens the main address" : "M'intix fuq in-netwerk tal-uffiċċju: dan il-madum jiftaħ l-indirizz ewlieni" }, "nplurals=4; plural=(n==1 ? 0 : n==0 || (n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20) ? 2 : 3);" ); diff --git a/l10n/mt.json b/l10n/mt.json index 05cab7037..562648e58 100644 --- a/l10n/mt.json +++ b/l10n/mt.json @@ -1333,6 +1333,19 @@ "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum", "{count} bookmark inqabeż: indirizzi tal-web biss jistgħu jsiru madum" - ] + ], + "Office networks": "Netwerks tal-uffiċċju", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Talbiet minn dawn in-netwerks jiftħu l-indirizz tal-uffiċċju tal-madum, jekk għandu wieħed. Daħħal firxa IPv4 jew IPv6 waħda f'kull linja, bħal 10.20.0.0/16.", + "Network ranges": "Firxiet tan-netwerk", + "Office networks saved.": "In-netwerks tal-uffiċċju ġew salvati.", + "Save office networks": "Issejvja n-netwerks tal-uffiċċju", + "Your current address {address} is on the office network.": "L-indirizz attwali tiegħek {address} jinsab fuq in-netwerk tal-uffiċċju.", + "Your current address {address} is not on the office network.": "L-indirizz attwali tiegħek {address} mhuwiex fuq in-netwerk tal-uffiċċju.", + "The office networks could not be loaded.": "In-netwerks tal-uffiċċju ma setgħux jitgħabbew.", + "The office networks could not be saved.": "In-netwerks tal-uffiċċju ma setgħux jiġu salvati.", + "Address on the office network": "Indirizz fuq in-netwerk tal-uffiċċju", + "Optional, for example http://zaken.intern": "Mhux obbligatorju, pereżempju http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Int fuq in-netwerk tal-uffiċċju: dan il-madum jiftaħ l-indirizz intern", + "You are not on the office network: this tile opens the main address": "M'intix fuq in-netwerk tal-uffiċċju: dan il-madum jiftaħ l-indirizz ewlieni" } } diff --git a/l10n/nb.js b/l10n/nb.js index 4112efb8a..934987ab9 100644 --- a/l10n/nb.js +++ b/l10n/nb.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Denne filen kunne ikke leses som en bokmerkefil.", "Room left on this dashboard: {count}. Pick fewer folders." : "Plass igjen på dette dashbordet: {count}. Velg færre mapper.", "The import failed; nothing was added." : "Importen mislyktes; ingenting ble lagt til.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmerke hoppet over: bare nettadresser kan bli fliser", "{count} bokmerker hoppet over: bare nettadresser kan bli fliser"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmerke hoppet over: bare nettadresser kan bli fliser", "{count} bokmerker hoppet over: bare nettadresser kan bli fliser"], + "Office networks" : "Kontornettverk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Forespørsler fra disse nettverkene åpner flisens kontoradresse, hvis den har en. Skriv inn ett IPv4- eller IPv6-område per linje, for eksempel 10.20.0.0/16.", + "Network ranges" : "Nettverksområder", + "Office networks saved." : "Kontornettverkene er lagret.", + "Save office networks" : "Lagre kontornettverk", + "Your current address {address} is on the office network." : "Din nåværende adresse {address} er på kontornettverket.", + "Your current address {address} is not on the office network." : "Din nåværende adresse {address} er ikke på kontornettverket.", + "The office networks could not be loaded." : "Kontornettverkene kunne ikke lastes inn.", + "The office networks could not be saved." : "Kontornettverkene kunne ikke lagres.", + "Address on the office network" : "Adresse på kontornettverket", + "Optional, for example http://zaken.intern" : "Valgfritt, for eksempel http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Du er på kontornettverket: denne flisen åpner den interne adressen", + "You are not on the office network: this tile opens the main address" : "Du er ikke på kontornettverket: denne flisen åpner hovedadressen" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nb.json b/l10n/nb.json index 3827381b1..3eeaf76d1 100644 --- a/l10n/nb.json +++ b/l10n/nb.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bokmerke hoppet over: bare nettadresser kan bli fliser", "{count} bokmerker hoppet over: bare nettadresser kan bli fliser" - ] + ], + "Office networks": "Kontornettverk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Forespørsler fra disse nettverkene åpner flisens kontoradresse, hvis den har en. Skriv inn ett IPv4- eller IPv6-område per linje, for eksempel 10.20.0.0/16.", + "Network ranges": "Nettverksområder", + "Office networks saved.": "Kontornettverkene er lagret.", + "Save office networks": "Lagre kontornettverk", + "Your current address {address} is on the office network.": "Din nåværende adresse {address} er på kontornettverket.", + "Your current address {address} is not on the office network.": "Din nåværende adresse {address} er ikke på kontornettverket.", + "The office networks could not be loaded.": "Kontornettverkene kunne ikke lastes inn.", + "The office networks could not be saved.": "Kontornettverkene kunne ikke lagres.", + "Address on the office network": "Adresse på kontornettverket", + "Optional, for example http://zaken.intern": "Valgfritt, for eksempel http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Du er på kontornettverket: denne flisen åpner den interne adressen", + "You are not on the office network: this tile opens the main address": "Du er ikke på kontornettverket: denne flisen åpner hovedadressen" } } diff --git a/l10n/nl.js b/l10n/nl.js index 02b167e0a..29bcf714c 100644 --- a/l10n/nl.js +++ b/l10n/nl.js @@ -1409,7 +1409,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Dit bestand kon niet als bladwijzerbestand worden gelezen.", "Room left on this dashboard: {count}. Pick fewer folders." : "Ruimte over op dit dashboard: {count}. Kies minder mappen.", "The import failed; nothing was added." : "Het importeren is mislukt; er is niets toegevoegd.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bladwijzer overgeslagen: alleen webadressen kunnen een tegel worden", "{count} bladwijzers overgeslagen: alleen webadressen kunnen een tegel worden"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bladwijzer overgeslagen: alleen webadressen kunnen een tegel worden", "{count} bladwijzers overgeslagen: alleen webadressen kunnen een tegel worden"], + "Office networks" : "Kantoornetwerken", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Verzoeken vanaf deze netwerken openen het kantooradres van een tegel, als die er een heeft. Vul per regel één IPv4- of IPv6-bereik in, zoals 10.20.0.0/16.", + "Network ranges" : "Netwerkbereiken", + "Office networks saved." : "Kantoornetwerken opgeslagen.", + "Save office networks" : "Kantoornetwerken opslaan", + "Your current address {address} is on the office network." : "Je huidige adres {address} zit op het kantoornetwerk.", + "Your current address {address} is not on the office network." : "Je huidige adres {address} zit niet op het kantoornetwerk.", + "The office networks could not be loaded." : "De kantoornetwerken konden niet worden geladen.", + "The office networks could not be saved." : "De kantoornetwerken konden niet worden opgeslagen.", + "Address on the office network" : "Adres op het kantoornetwerk", + "Optional, for example http://zaken.intern" : "Optioneel, bijvoorbeeld http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Je zit op het kantoornetwerk: deze tegel opent het interne adres", + "You are not on the office network: this tile opens the main address" : "Je zit niet op het kantoornetwerk: deze tegel opent het hoofdadres" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/nl.json b/l10n/nl.json index 022449bad..dd721e263 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -1389,6 +1389,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bladwijzer overgeslagen: alleen webadressen kunnen een tegel worden", "{count} bladwijzers overgeslagen: alleen webadressen kunnen een tegel worden" - ] + ], + "Office networks": "Kantoornetwerken", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Verzoeken vanaf deze netwerken openen het kantooradres van een tegel, als die er een heeft. Vul per regel één IPv4- of IPv6-bereik in, zoals 10.20.0.0/16.", + "Network ranges": "Netwerkbereiken", + "Office networks saved.": "Kantoornetwerken opgeslagen.", + "Save office networks": "Kantoornetwerken opslaan", + "Your current address {address} is on the office network.": "Je huidige adres {address} zit op het kantoornetwerk.", + "Your current address {address} is not on the office network.": "Je huidige adres {address} zit niet op het kantoornetwerk.", + "The office networks could not be loaded.": "De kantoornetwerken konden niet worden geladen.", + "The office networks could not be saved.": "De kantoornetwerken konden niet worden opgeslagen.", + "Address on the office network": "Adres op het kantoornetwerk", + "Optional, for example http://zaken.intern": "Optioneel, bijvoorbeeld http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Je zit op het kantoornetwerk: deze tegel opent het interne adres", + "You are not on the office network: this tile opens the main address": "Je zit niet op het kantoornetwerk: deze tegel opent het hoofdadres" } } diff --git a/l10n/pl.js b/l10n/pl.js index 605c71a94..d902cfef0 100644 --- a/l10n/pl.js +++ b/l10n/pl.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Nie udało się odczytać tego pliku jako pliku zakładek.", "Room left on this dashboard: {count}. Pick fewer folders." : "Wolne miejsce na tym pulpicie: {count}. Wybierz mniej folderów.", "The import failed; nothing was added." : "Import nie powiódł się; nic nie dodano.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Pominięto {count} zakładkę: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładki: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładek: kafelkiem może zostać tylko adres internetowy"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Pominięto {count} zakładkę: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładki: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładek: kafelkiem może zostać tylko adres internetowy"], + "Office networks" : "Sieci biurowe", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Żądania z tych sieci otwierają biurowy adres kafelka, jeśli go ma. Wpisz jeden zakres IPv4 lub IPv6 w wierszu, np. 10.20.0.0/16.", + "Network ranges" : "Zakresy sieci", + "Office networks saved." : "Sieci biurowe zapisane.", + "Save office networks" : "Zapisz sieci biurowe", + "Your current address {address} is on the office network." : "Twój bieżący adres {address} jest w sieci biurowej.", + "Your current address {address} is not on the office network." : "Twój bieżący adres {address} nie jest w sieci biurowej.", + "The office networks could not be loaded." : "Nie udało się wczytać sieci biurowych.", + "The office networks could not be saved." : "Nie udało się zapisać sieci biurowych.", + "Address on the office network" : "Adres w sieci biurowej", + "Optional, for example http://zaken.intern" : "Opcjonalnie, np. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Jesteś w sieci biurowej: ten kafelek otwiera adres wewnętrzny", + "You are not on the office network: this tile opens the main address" : "Nie jesteś w sieci biurowej: ten kafelek otwiera adres główny" }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/pl.json b/l10n/pl.json index fbd69e43d..cf64cfc6f 100644 --- a/l10n/pl.json +++ b/l10n/pl.json @@ -1331,6 +1331,19 @@ "Pominięto {count} zakładkę: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładki: kafelkiem może zostać tylko adres internetowy", "Pominięto {count} zakładek: kafelkiem może zostać tylko adres internetowy" - ] + ], + "Office networks": "Sieci biurowe", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Żądania z tych sieci otwierają biurowy adres kafelka, jeśli go ma. Wpisz jeden zakres IPv4 lub IPv6 w wierszu, np. 10.20.0.0/16.", + "Network ranges": "Zakresy sieci", + "Office networks saved.": "Sieci biurowe zapisane.", + "Save office networks": "Zapisz sieci biurowe", + "Your current address {address} is on the office network.": "Twój bieżący adres {address} jest w sieci biurowej.", + "Your current address {address} is not on the office network.": "Twój bieżący adres {address} nie jest w sieci biurowej.", + "The office networks could not be loaded.": "Nie udało się wczytać sieci biurowych.", + "The office networks could not be saved.": "Nie udało się zapisać sieci biurowych.", + "Address on the office network": "Adres w sieci biurowej", + "Optional, for example http://zaken.intern": "Opcjonalnie, np. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Jesteś w sieci biurowej: ten kafelek otwiera adres wewnętrzny", + "You are not on the office network: this tile opens the main address": "Nie jesteś w sieci biurowej: ten kafelek otwiera adres główny" } } diff --git a/l10n/pt.js b/l10n/pt.js index fa6ca804c..e6ed34a9a 100644 --- a/l10n/pt.js +++ b/l10n/pt.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Não foi possível ler este ficheiro como ficheiro de marcadores.", "Room left on this dashboard: {count}. Pick fewer folders." : "Espaço restante neste painel: {count}. Escolha menos pastas.", "The import failed; nothing was added." : "A importação falhou; nada foi adicionado.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcador ignorado: só endereços web podem ser mosaicos", "{count} marcadores ignorados: só endereços web podem ser mosaicos"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcador ignorado: só endereços web podem ser mosaicos", "{count} marcadores ignorados: só endereços web podem ser mosaicos"], + "Office networks" : "Redes do escritório", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Os pedidos destas redes abrem o endereço de escritório do mosaico, se tiver um. Introduza um intervalo IPv4 ou IPv6 por linha, como 10.20.0.0/16.", + "Network ranges" : "Intervalos de rede", + "Office networks saved." : "Redes do escritório guardadas.", + "Save office networks" : "Guardar redes do escritório", + "Your current address {address} is on the office network." : "O seu endereço atual {address} está na rede do escritório.", + "Your current address {address} is not on the office network." : "O seu endereço atual {address} não está na rede do escritório.", + "The office networks could not be loaded." : "Não foi possível carregar as redes do escritório.", + "The office networks could not be saved." : "Não foi possível guardar as redes do escritório.", + "Address on the office network" : "Endereço na rede do escritório", + "Optional, for example http://zaken.intern" : "Opcional, por exemplo http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Está na rede do escritório: este mosaico abre o endereço interno", + "You are not on the office network: this tile opens the main address" : "Não está na rede do escritório: este mosaico abre o endereço principal" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/pt.json b/l10n/pt.json index c309aaf4f..6e498273a 100644 --- a/l10n/pt.json +++ b/l10n/pt.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} marcador ignorado: só endereços web podem ser mosaicos", "{count} marcadores ignorados: só endereços web podem ser mosaicos" - ] + ], + "Office networks": "Redes do escritório", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Os pedidos destas redes abrem o endereço de escritório do mosaico, se tiver um. Introduza um intervalo IPv4 ou IPv6 por linha, como 10.20.0.0/16.", + "Network ranges": "Intervalos de rede", + "Office networks saved.": "Redes do escritório guardadas.", + "Save office networks": "Guardar redes do escritório", + "Your current address {address} is on the office network.": "O seu endereço atual {address} está na rede do escritório.", + "Your current address {address} is not on the office network.": "O seu endereço atual {address} não está na rede do escritório.", + "The office networks could not be loaded.": "Não foi possível carregar as redes do escritório.", + "The office networks could not be saved.": "Não foi possível guardar as redes do escritório.", + "Address on the office network": "Endereço na rede do escritório", + "Optional, for example http://zaken.intern": "Opcional, por exemplo http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Está na rede do escritório: este mosaico abre o endereço interno", + "You are not on the office network: this tile opens the main address": "Não está na rede do escritório: este mosaico abre o endereço principal" } } diff --git a/l10n/rm.js b/l10n/rm.js index cf2621461..7daa0e5c0 100644 --- a/l10n/rm.js +++ b/l10n/rm.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Questa datoteca na po betg vegnir legida sco datoteca da segnapaginas.", "Room left on this dashboard: {count}. Pick fewer folders." : "Plaz restanta sin quest dashboard: {count}. Tscherna main ordinaturs.", "The import failed; nothing was added." : "L'import n'è betg reussì; nagut n'è vegnì agiuntà.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnapaginas sursiglì: mo adressas web pon daventar quadrels", "{count} segnapaginas sursiglids: mo adressas web pon daventar quadrels"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} segnapaginas sursiglì: mo adressas web pon daventar quadrels", "{count} segnapaginas sursiglids: mo adressas web pon daventar quadrels"], + "Office networks" : "Raits dal biro", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Dumondas da quests raits avran l'adressa da biro dal quadrel, sch'el ha ina. Endatescha ina zona IPv4 u IPv6 per lingia, sco 10.20.0.0/16.", + "Network ranges" : "Zonas da rait", + "Office networks saved." : "Raits dal biro memorisadas.", + "Save office networks" : "Memorisar las raits dal biro", + "Your current address {address} is on the office network." : "Tia adressa actuala {address} è en la rait dal biro.", + "Your current address {address} is not on the office network." : "Tia adressa actuala {address} n'è betg en la rait dal biro.", + "The office networks could not be loaded." : "I n'è betg reussì da chargiar las raits dal biro.", + "The office networks could not be saved." : "I n'è betg reussì da memorisar las raits dal biro.", + "Address on the office network" : "Adressa en la rait dal biro", + "Optional, for example http://zaken.intern" : "Facultativ, per exempel http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ti es en la rait dal biro: quest quadrel avra l'adressa interna", + "You are not on the office network: this tile opens the main address" : "Ti n'es betg en la rait dal biro: quest quadrel avra l'adressa principala" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/rm.json b/l10n/rm.json index 6deef3e07..7183c2cc9 100644 --- a/l10n/rm.json +++ b/l10n/rm.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} segnapaginas sursiglì: mo adressas web pon daventar quadrels", "{count} segnapaginas sursiglids: mo adressas web pon daventar quadrels" - ] + ], + "Office networks": "Raits dal biro", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Dumondas da quests raits avran l'adressa da biro dal quadrel, sch'el ha ina. Endatescha ina zona IPv4 u IPv6 per lingia, sco 10.20.0.0/16.", + "Network ranges": "Zonas da rait", + "Office networks saved.": "Raits dal biro memorisadas.", + "Save office networks": "Memorisar las raits dal biro", + "Your current address {address} is on the office network.": "Tia adressa actuala {address} è en la rait dal biro.", + "Your current address {address} is not on the office network.": "Tia adressa actuala {address} n'è betg en la rait dal biro.", + "The office networks could not be loaded.": "I n'è betg reussì da chargiar las raits dal biro.", + "The office networks could not be saved.": "I n'è betg reussì da memorisar las raits dal biro.", + "Address on the office network": "Adressa en la rait dal biro", + "Optional, for example http://zaken.intern": "Facultativ, per exempel http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ti es en la rait dal biro: quest quadrel avra l'adressa interna", + "You are not on the office network: this tile opens the main address": "Ti n'es betg en la rait dal biro: quest quadrel avra l'adressa principala" } } diff --git a/l10n/ro.js b/l10n/ro.js index 2741eca68..39843bce1 100644 --- a/l10n/ro.js +++ b/l10n/ro.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Acest fișier nu a putut fi citit ca fișier de marcaje.", "Room left on this dashboard: {count}. Pick fewer folders." : "Spațiu rămas pe acest tablou de bord: {count}. Alege mai puține dosare.", "The import failed; nothing was added." : "Importul a eșuat; nu s-a adăugat nimic.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcaj omis: doar adresele web pot deveni dale", "{count} marcaje omise: doar adresele web pot deveni dale", "{count} de marcaje omise: doar adresele web pot deveni dale"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} marcaj omis: doar adresele web pot deveni dale", "{count} marcaje omise: doar adresele web pot deveni dale", "{count} de marcaje omise: doar adresele web pot deveni dale"], + "Office networks" : "Rețele de birou", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Cererile din aceste rețele deschid adresa de birou a dalei, dacă are una. Introdu câte un interval IPv4 sau IPv6 pe linie, de exemplu 10.20.0.0/16.", + "Network ranges" : "Intervale de rețea", + "Office networks saved." : "Rețelele de birou au fost salvate.", + "Save office networks" : "Salvează rețelele de birou", + "Your current address {address} is on the office network." : "Adresa ta actuală {address} este în rețeaua de birou.", + "Your current address {address} is not on the office network." : "Adresa ta actuală {address} nu este în rețeaua de birou.", + "The office networks could not be loaded." : "Rețelele de birou nu au putut fi încărcate.", + "The office networks could not be saved." : "Rețelele de birou nu au putut fi salvate.", + "Address on the office network" : "Adresă în rețeaua de birou", + "Optional, for example http://zaken.intern" : "Opțional, de exemplu http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ești în rețeaua de birou: această dală deschide adresa internă", + "You are not on the office network: this tile opens the main address" : "Nu ești în rețeaua de birou: această dală deschide adresa principală" }, "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);" ); diff --git a/l10n/ro.json b/l10n/ro.json index 7bcda640a..c3741782a 100644 --- a/l10n/ro.json +++ b/l10n/ro.json @@ -1331,6 +1331,19 @@ "{count} marcaj omis: doar adresele web pot deveni dale", "{count} marcaje omise: doar adresele web pot deveni dale", "{count} de marcaje omise: doar adresele web pot deveni dale" - ] + ], + "Office networks": "Rețele de birou", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Cererile din aceste rețele deschid adresa de birou a dalei, dacă are una. Introdu câte un interval IPv4 sau IPv6 pe linie, de exemplu 10.20.0.0/16.", + "Network ranges": "Intervale de rețea", + "Office networks saved.": "Rețelele de birou au fost salvate.", + "Save office networks": "Salvează rețelele de birou", + "Your current address {address} is on the office network.": "Adresa ta actuală {address} este în rețeaua de birou.", + "Your current address {address} is not on the office network.": "Adresa ta actuală {address} nu este în rețeaua de birou.", + "The office networks could not be loaded.": "Rețelele de birou nu au putut fi încărcate.", + "The office networks could not be saved.": "Rețelele de birou nu au putut fi salvate.", + "Address on the office network": "Adresă în rețeaua de birou", + "Optional, for example http://zaken.intern": "Opțional, de exemplu http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ești în rețeaua de birou: această dală deschide adresa internă", + "You are not on the office network: this tile opens the main address": "Nu ești în rețeaua de birou: această dală deschide adresa principală" } } diff --git a/l10n/ru.js b/l10n/ru.js index 723a7e949..2ed8faf9a 100644 --- a/l10n/ru.js +++ b/l10n/ru.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Этот файл не удалось прочитать как файл закладок.", "Room left on this dashboard: {count}. Pick fewer folders." : "Осталось места на этой панели: {count}. Выберите меньше папок.", "The import failed; nothing was added." : "Импорт не удался; ничего не добавлено.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущена {count} закладка: плиткой может стать только веб-адрес", "Пропущено {count} закладки: плиткой может стать только веб-адрес", "Пропущено {count} закладок: плиткой может стать только веб-адрес"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущена {count} закладка: плиткой может стать только веб-адрес", "Пропущено {count} закладки: плиткой может стать только веб-адрес", "Пропущено {count} закладок: плиткой может стать только веб-адрес"], + "Office networks" : "Офисные сети", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Запросы из этих сетей открывают офисный адрес плитки, если он есть. Введите по одному диапазону IPv4 или IPv6 в строке, например 10.20.0.0/16.", + "Network ranges" : "Диапазоны сетей", + "Office networks saved." : "Офисные сети сохранены.", + "Save office networks" : "Сохранить офисные сети", + "Your current address {address} is on the office network." : "Ваш текущий адрес {address} находится в офисной сети.", + "Your current address {address} is not on the office network." : "Ваш текущий адрес {address} не находится в офисной сети.", + "The office networks could not be loaded." : "Не удалось загрузить офисные сети.", + "The office networks could not be saved." : "Не удалось сохранить офисные сети.", + "Address on the office network" : "Адрес в офисной сети", + "Optional, for example http://zaken.intern" : "Необязательно, например http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Вы в офисной сети: эта плитка открывает внутренний адрес", + "You are not on the office network: this tile opens the main address" : "Вы не в офисной сети: эта плитка открывает основной адрес" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/ru.json b/l10n/ru.json index 025166aa3..750ebc92e 100644 --- a/l10n/ru.json +++ b/l10n/ru.json @@ -1331,6 +1331,19 @@ "Пропущена {count} закладка: плиткой может стать только веб-адрес", "Пропущено {count} закладки: плиткой может стать только веб-адрес", "Пропущено {count} закладок: плиткой может стать только веб-адрес" - ] + ], + "Office networks": "Офисные сети", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Запросы из этих сетей открывают офисный адрес плитки, если он есть. Введите по одному диапазону IPv4 или IPv6 в строке, например 10.20.0.0/16.", + "Network ranges": "Диапазоны сетей", + "Office networks saved.": "Офисные сети сохранены.", + "Save office networks": "Сохранить офисные сети", + "Your current address {address} is on the office network.": "Ваш текущий адрес {address} находится в офисной сети.", + "Your current address {address} is not on the office network.": "Ваш текущий адрес {address} не находится в офисной сети.", + "The office networks could not be loaded.": "Не удалось загрузить офисные сети.", + "The office networks could not be saved.": "Не удалось сохранить офисные сети.", + "Address on the office network": "Адрес в офисной сети", + "Optional, for example http://zaken.intern": "Необязательно, например http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Вы в офисной сети: эта плитка открывает внутренний адрес", + "You are not on the office network: this tile opens the main address": "Вы не в офисной сети: эта плитка открывает основной адрес" } } diff --git a/l10n/sk.js b/l10n/sk.js index a8791a51d..21d63997d 100644 --- a/l10n/sk.js +++ b/l10n/sk.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Tento súbor sa nepodarilo prečítať ako súbor záložiek.", "Room left on this dashboard: {count}. Pick fewer folders." : "Zostávajúce miesto na tomto paneli: {count}. Vyberte menej priečinkov.", "The import failed; nothing was added." : "Import zlyhal; nič nebolo pridané.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočená {count} záložka: dlaždicou môže byť len webová adresa", "Preskočené {count} záložky: dlaždicou môže byť len webová adresa", "Preskočených {count} záložiek: dlaždicou môže byť len webová adresa"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočená {count} záložka: dlaždicou môže byť len webová adresa", "Preskočené {count} záložky: dlaždicou môže byť len webová adresa", "Preskočených {count} záložiek: dlaždicou môže byť len webová adresa"], + "Office networks" : "Kancelárske siete", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Požiadavky z týchto sietí otvárajú kancelársku adresu dlaždice, ak ju má. Zadajte jeden rozsah IPv4 alebo IPv6 na riadok, napríklad 10.20.0.0/16.", + "Network ranges" : "Rozsahy sietí", + "Office networks saved." : "Kancelárske siete sú uložené.", + "Save office networks" : "Uložiť kancelárske siete", + "Your current address {address} is on the office network." : "Vaša aktuálna adresa {address} je v kancelárskej sieti.", + "Your current address {address} is not on the office network." : "Vaša aktuálna adresa {address} nie je v kancelárskej sieti.", + "The office networks could not be loaded." : "Kancelárske siete sa nepodarilo načítať.", + "The office networks could not be saved." : "Kancelárske siete sa nepodarilo uložiť.", + "Address on the office network" : "Adresa v kancelárskej sieti", + "Optional, for example http://zaken.intern" : "Nepovinné, napríklad http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ste v kancelárskej sieti: táto dlaždica otvorí internú adresu", + "You are not on the office network: this tile opens the main address" : "Nie ste v kancelárskej sieti: táto dlaždica otvorí hlavnú adresu" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" ); diff --git a/l10n/sk.json b/l10n/sk.json index bba7099b6..68aab18eb 100644 --- a/l10n/sk.json +++ b/l10n/sk.json @@ -1331,6 +1331,19 @@ "Preskočená {count} záložka: dlaždicou môže byť len webová adresa", "Preskočené {count} záložky: dlaždicou môže byť len webová adresa", "Preskočených {count} záložiek: dlaždicou môže byť len webová adresa" - ] + ], + "Office networks": "Kancelárske siete", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Požiadavky z týchto sietí otvárajú kancelársku adresu dlaždice, ak ju má. Zadajte jeden rozsah IPv4 alebo IPv6 na riadok, napríklad 10.20.0.0/16.", + "Network ranges": "Rozsahy sietí", + "Office networks saved.": "Kancelárske siete sú uložené.", + "Save office networks": "Uložiť kancelárske siete", + "Your current address {address} is on the office network.": "Vaša aktuálna adresa {address} je v kancelárskej sieti.", + "Your current address {address} is not on the office network.": "Vaša aktuálna adresa {address} nie je v kancelárskej sieti.", + "The office networks could not be loaded.": "Kancelárske siete sa nepodarilo načítať.", + "The office networks could not be saved.": "Kancelárske siete sa nepodarilo uložiť.", + "Address on the office network": "Adresa v kancelárskej sieti", + "Optional, for example http://zaken.intern": "Nepovinné, napríklad http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ste v kancelárskej sieti: táto dlaždica otvorí internú adresu", + "You are not on the office network: this tile opens the main address": "Nie ste v kancelárskej sieti: táto dlaždica otvorí hlavnú adresu" } } diff --git a/l10n/sl.js b/l10n/sl.js index c7d57f454..ec1661cfb 100644 --- a/l10n/sl.js +++ b/l10n/sl.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Te datoteke ni bilo mogoče prebrati kot datoteke z zaznamki.", "Room left on this dashboard: {count}. Pick fewer folders." : "Preostali prostor na tej nadzorni plošči: {count}. Izberite manj map.", "The import failed; nothing was added." : "Uvoz ni uspel; nič ni bilo dodano.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočen {count} zaznamek: ploščica je lahko le spletni naslov", "Preskočena {count} zaznamka: ploščica je lahko le spletni naslov", "Preskočeni {count} zaznamki: ploščica je lahko le spletni naslov", "Preskočenih {count} zaznamkov: ploščica je lahko le spletni naslov"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Preskočen {count} zaznamek: ploščica je lahko le spletni naslov", "Preskočena {count} zaznamka: ploščica je lahko le spletni naslov", "Preskočeni {count} zaznamki: ploščica je lahko le spletni naslov", "Preskočenih {count} zaznamkov: ploščica je lahko le spletni naslov"], + "Office networks" : "Pisarniška omrežja", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Zahteve iz teh omrežij odprejo pisarniški naslov ploščice, če ga ima. Vnesite en obseg IPv4 ali IPv6 na vrstico, na primer 10.20.0.0/16.", + "Network ranges" : "Omrežni obsegi", + "Office networks saved." : "Pisarniška omrežja so shranjena.", + "Save office networks" : "Shrani pisarniška omrežja", + "Your current address {address} is on the office network." : "Vaš trenutni naslov {address} je v pisarniškem omrežju.", + "Your current address {address} is not on the office network." : "Vaš trenutni naslov {address} ni v pisarniškem omrežju.", + "The office networks could not be loaded." : "Pisarniških omrežij ni bilo mogoče naložiti.", + "The office networks could not be saved." : "Pisarniških omrežij ni bilo mogoče shraniti.", + "Address on the office network" : "Naslov v pisarniškem omrežju", + "Optional, for example http://zaken.intern" : "Neobvezno, na primer http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ste v pisarniškem omrežju: ta ploščica odpre notranji naslov", + "You are not on the office network: this tile opens the main address" : "Niste v pisarniškem omrežju: ta ploščica odpre glavni naslov" }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" ); diff --git a/l10n/sl.json b/l10n/sl.json index 87d99fe55..ee5f9ba1f 100644 --- a/l10n/sl.json +++ b/l10n/sl.json @@ -1333,6 +1333,19 @@ "Preskočena {count} zaznamka: ploščica je lahko le spletni naslov", "Preskočeni {count} zaznamki: ploščica je lahko le spletni naslov", "Preskočenih {count} zaznamkov: ploščica je lahko le spletni naslov" - ] + ], + "Office networks": "Pisarniška omrežja", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Zahteve iz teh omrežij odprejo pisarniški naslov ploščice, če ga ima. Vnesite en obseg IPv4 ali IPv6 na vrstico, na primer 10.20.0.0/16.", + "Network ranges": "Omrežni obsegi", + "Office networks saved.": "Pisarniška omrežja so shranjena.", + "Save office networks": "Shrani pisarniška omrežja", + "Your current address {address} is on the office network.": "Vaš trenutni naslov {address} je v pisarniškem omrežju.", + "Your current address {address} is not on the office network.": "Vaš trenutni naslov {address} ni v pisarniškem omrežju.", + "The office networks could not be loaded.": "Pisarniških omrežij ni bilo mogoče naložiti.", + "The office networks could not be saved.": "Pisarniških omrežij ni bilo mogoče shraniti.", + "Address on the office network": "Naslov v pisarniškem omrežju", + "Optional, for example http://zaken.intern": "Neobvezno, na primer http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ste v pisarniškem omrežju: ta ploščica odpre notranji naslov", + "You are not on the office network: this tile opens the main address": "Niste v pisarniškem omrežju: ta ploščica odpre glavni naslov" } } diff --git a/l10n/sq.js b/l10n/sq.js index 6160dad4d..e83694dd9 100644 --- a/l10n/sq.js +++ b/l10n/sq.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ky skedar nuk u lexua dot si skedar faqerojtësish.", "Room left on this dashboard: {count}. Pick fewer folders." : "Hapësirë e mbetur në këtë panel: {count}. Zgjidh më pak dosje.", "The import failed; nothing was added." : "Importimi dështoi; nuk u shtua asgjë.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["U anashkalua {count} faqerojtës: vetëm adresat web mund të bëhen pllaka", "U anashkaluan {count} faqerojtës: vetëm adresat web mund të bëhen pllaka"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["U anashkalua {count} faqerojtës: vetëm adresat web mund të bëhen pllaka", "U anashkaluan {count} faqerojtës: vetëm adresat web mund të bëhen pllaka"], + "Office networks" : "Rrjetet e zyrës", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Kërkesat nga këto rrjete hapin adresën e zyrës së pllakës, nëse ka një të tillë. Shkruaj një varg IPv4 ose IPv6 për rresht, si 10.20.0.0/16.", + "Network ranges" : "Vargje rrjeti", + "Office networks saved." : "Rrjetet e zyrës u ruajtën.", + "Save office networks" : "Ruaj rrjetet e zyrës", + "Your current address {address} is on the office network." : "Adresa jote aktuale {address} është në rrjetin e zyrës.", + "Your current address {address} is not on the office network." : "Adresa jote aktuale {address} nuk është në rrjetin e zyrës.", + "The office networks could not be loaded." : "Rrjetet e zyrës nuk u ngarkuan.", + "The office networks could not be saved." : "Rrjetet e zyrës nuk u ruajtën.", + "Address on the office network" : "Adresa në rrjetin e zyrës", + "Optional, for example http://zaken.intern" : "Opsionale, p.sh. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Je në rrjetin e zyrës: kjo pllakë hap adresën e brendshme", + "You are not on the office network: this tile opens the main address" : "Nuk je në rrjetin e zyrës: kjo pllakë hap adresën kryesore" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sq.json b/l10n/sq.json index e8e26ffc0..2f2eba78c 100644 --- a/l10n/sq.json +++ b/l10n/sq.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "U anashkalua {count} faqerojtës: vetëm adresat web mund të bëhen pllaka", "U anashkaluan {count} faqerojtës: vetëm adresat web mund të bëhen pllaka" - ] + ], + "Office networks": "Rrjetet e zyrës", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Kërkesat nga këto rrjete hapin adresën e zyrës së pllakës, nëse ka një të tillë. Shkruaj një varg IPv4 ose IPv6 për rresht, si 10.20.0.0/16.", + "Network ranges": "Vargje rrjeti", + "Office networks saved.": "Rrjetet e zyrës u ruajtën.", + "Save office networks": "Ruaj rrjetet e zyrës", + "Your current address {address} is on the office network.": "Adresa jote aktuale {address} është në rrjetin e zyrës.", + "Your current address {address} is not on the office network.": "Adresa jote aktuale {address} nuk është në rrjetin e zyrës.", + "The office networks could not be loaded.": "Rrjetet e zyrës nuk u ngarkuan.", + "The office networks could not be saved.": "Rrjetet e zyrës nuk u ruajtën.", + "Address on the office network": "Adresa në rrjetin e zyrës", + "Optional, for example http://zaken.intern": "Opsionale, p.sh. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Je në rrjetin e zyrës: kjo pllakë hap adresën e brendshme", + "You are not on the office network: this tile opens the main address": "Nuk je në rrjetin e zyrës: kjo pllakë hap adresën kryesore" } } diff --git a/l10n/sr.js b/l10n/sr.js index ac7bf0a0d..77732f00b 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -1350,6 +1350,19 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Ова датотека не може да се прочита као датотека обележивача.", "Room left on this dashboard: {count}. Pick fewer folders." : "Преостало места на овој контролној табли: {count}. Изаберите мање фасцикли.", "The import failed; nothing was added." : "Увоз није успео; ништа није додато.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескочен {count} обележивач: само веб адресе могу постати плочице", "Прескочена {count} обележивача: само веб адресе могу постати плочице", "Прескочено {count} обележивача: само веб адресе могу постати плочице"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Прескочен {count} обележивач: само веб адресе могу постати плочице", "Прескочена {count} обележивача: само веб адресе могу постати плочице", "Прескочено {count} обележивача: само веб адресе могу постати плочице"], + "Office networks" : "Канцеларијске мреже", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Захтеви из ових мрежа отварају канцеларијску адресу плочице, ако је има. Унесите један IPv4 или IPv6 опсег по реду, нпр. 10.20.0.0/16.", + "Network ranges" : "Мрежни опсези", + "Office networks saved." : "Канцеларијске мреже су сачуване.", + "Save office networks" : "Сачувај канцеларијске мреже", + "Your current address {address} is on the office network." : "Ваша тренутна адреса {address} је у канцеларијској мрежи.", + "Your current address {address} is not on the office network." : "Ваша тренутна адреса {address} није у канцеларијској мрежи.", + "The office networks could not be loaded." : "Канцеларијске мреже нису могле да се учитају.", + "The office networks could not be saved." : "Канцеларијске мреже нису могле да се сачувају.", + "Address on the office network" : "Адреса у канцеларијској мрежи", + "Optional, for example http://zaken.intern" : "Необавезно, нпр. http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ви сте у канцеларијској мрежи: ова плочица отвара интерну адресу", + "You are not on the office network: this tile opens the main address" : "Нисте у канцеларијској мрежи: ова плочица отвара главну адресу" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/l10n/sr.json b/l10n/sr.json index 2fe773f5e..4bed74627 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -1331,6 +1331,19 @@ "Прескочен {count} обележивач: само веб адресе могу постати плочице", "Прескочена {count} обележивача: само веб адресе могу постати плочице", "Прескочено {count} обележивача: само веб адресе могу постати плочице" - ] + ], + "Office networks": "Канцеларијске мреже", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Захтеви из ових мрежа отварају канцеларијску адресу плочице, ако је има. Унесите један IPv4 или IPv6 опсег по реду, нпр. 10.20.0.0/16.", + "Network ranges": "Мрежни опсези", + "Office networks saved.": "Канцеларијске мреже су сачуване.", + "Save office networks": "Сачувај канцеларијске мреже", + "Your current address {address} is on the office network.": "Ваша тренутна адреса {address} је у канцеларијској мрежи.", + "Your current address {address} is not on the office network.": "Ваша тренутна адреса {address} није у канцеларијској мрежи.", + "The office networks could not be loaded.": "Канцеларијске мреже нису могле да се учитају.", + "The office networks could not be saved.": "Канцеларијске мреже нису могле да се сачувају.", + "Address on the office network": "Адреса у канцеларијској мрежи", + "Optional, for example http://zaken.intern": "Необавезно, нпр. http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ви сте у канцеларијској мрежи: ова плочица отвара интерну адресу", + "You are not on the office network: this tile opens the main address": "Нисте у канцеларијској мрежи: ова плочица отвара главну адресу" } } diff --git a/l10n/sv.js b/l10n/sv.js index 92a458d91..9ea211ee8 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Filen kunde inte läsas som en bokmärkesfil.", "Room left on this dashboard: {count}. Pick fewer folders." : "Plats kvar på den här instrumentpanelen: {count}. Välj färre mappar.", "The import failed; nothing was added." : "Importen misslyckades; inget lades till.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmärke hoppades över: bara webbadresser kan bli paneler", "{count} bokmärken hoppades över: bara webbadresser kan bli paneler"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} bokmärke hoppades över: bara webbadresser kan bli paneler", "{count} bokmärken hoppades över: bara webbadresser kan bli paneler"], + "Office networks" : "Kontorsnätverk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Förfrågningar från de här näten öppnar panelens kontorsadress, om den har en. Ange ett IPv4- eller IPv6-intervall per rad, till exempel 10.20.0.0/16.", + "Network ranges" : "Nätverksintervall", + "Office networks saved." : "Kontorsnätverken har sparats.", + "Save office networks" : "Spara kontorsnätverk", + "Your current address {address} is on the office network." : "Din nuvarande adress {address} finns på kontorsnätverket.", + "Your current address {address} is not on the office network." : "Din nuvarande adress {address} finns inte på kontorsnätverket.", + "The office networks could not be loaded." : "Kontorsnätverken kunde inte läsas in.", + "The office networks could not be saved." : "Kontorsnätverken kunde inte sparas.", + "Address on the office network" : "Adress på kontorsnätverket", + "Optional, for example http://zaken.intern" : "Valfritt, till exempel http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Du är på kontorsnätverket: den här panelen öppnar den interna adressen", + "You are not on the office network: this tile opens the main address" : "Du är inte på kontorsnätverket: den här panelen öppnar huvudadressen" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/sv.json b/l10n/sv.json index 90948ab96..020837145 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} bokmärke hoppades över: bara webbadresser kan bli paneler", "{count} bokmärken hoppades över: bara webbadresser kan bli paneler" - ] + ], + "Office networks": "Kontorsnätverk", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Förfrågningar från de här näten öppnar panelens kontorsadress, om den har en. Ange ett IPv4- eller IPv6-intervall per rad, till exempel 10.20.0.0/16.", + "Network ranges": "Nätverksintervall", + "Office networks saved.": "Kontorsnätverken har sparats.", + "Save office networks": "Spara kontorsnätverk", + "Your current address {address} is on the office network.": "Din nuvarande adress {address} finns på kontorsnätverket.", + "Your current address {address} is not on the office network.": "Din nuvarande adress {address} finns inte på kontorsnätverket.", + "The office networks could not be loaded.": "Kontorsnätverken kunde inte läsas in.", + "The office networks could not be saved.": "Kontorsnätverken kunde inte sparas.", + "Address on the office network": "Adress på kontorsnätverket", + "Optional, for example http://zaken.intern": "Valfritt, till exempel http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Du är på kontorsnätverket: den här panelen öppnar den interna adressen", + "You are not on the office network: this tile opens the main address": "Du är inte på kontorsnätverket: den här panelen öppnar huvudadressen" } } diff --git a/l10n/tr.js b/l10n/tr.js index 8697db854..c9013f170 100644 --- a/l10n/tr.js +++ b/l10n/tr.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Bu dosya bir yer imi dosyası olarak okunamadı.", "Room left on this dashboard: {count}. Pick fewer folders." : "Bu panoda kalan yer: {count}. Daha az klasör seç.", "The import failed; nothing was added." : "İçe aktarma başarısız oldu; hiçbir şey eklenmedi.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir", "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir", "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir"], + "Office networks" : "Ofis ağları", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Bu ağlardan gelen istekler, varsa kutucuğun ofis adresini açar. Her satıra bir IPv4 veya IPv6 aralığı gir, örneğin 10.20.0.0/16.", + "Network ranges" : "Ağ aralıkları", + "Office networks saved." : "Ofis ağları kaydedildi.", + "Save office networks" : "Ofis ağlarını kaydet", + "Your current address {address} is on the office network." : "Şu anki adresin {address} ofis ağında.", + "Your current address {address} is not on the office network." : "Şu anki adresin {address} ofis ağında değil.", + "The office networks could not be loaded." : "Ofis ağları yüklenemedi.", + "The office networks could not be saved." : "Ofis ağları kaydedilemedi.", + "Address on the office network" : "Ofis ağındaki adres", + "Optional, for example http://zaken.intern" : "İsteğe bağlı, örneğin http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ofis ağındasın: bu kutucuk iç adresi açar", + "You are not on the office network: this tile opens the main address" : "Ofis ağında değilsin: bu kutucuk ana adresi açar" }, "nplurals=2; plural=(n != 1);" ); diff --git a/l10n/tr.json b/l10n/tr.json index 2181ac1fc..e1da6a308 100644 --- a/l10n/tr.json +++ b/l10n/tr.json @@ -1329,6 +1329,19 @@ "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_": [ "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir", "{count} yer imi atlandı: yalnızca web adresleri kutucuk olabilir" - ] + ], + "Office networks": "Ofis ağları", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Bu ağlardan gelen istekler, varsa kutucuğun ofis adresini açar. Her satıra bir IPv4 veya IPv6 aralığı gir, örneğin 10.20.0.0/16.", + "Network ranges": "Ağ aralıkları", + "Office networks saved.": "Ofis ağları kaydedildi.", + "Save office networks": "Ofis ağlarını kaydet", + "Your current address {address} is on the office network.": "Şu anki adresin {address} ofis ağında.", + "Your current address {address} is not on the office network.": "Şu anki adresin {address} ofis ağında değil.", + "The office networks could not be loaded.": "Ofis ağları yüklenemedi.", + "The office networks could not be saved.": "Ofis ağları kaydedilemedi.", + "Address on the office network": "Ofis ağındaki adres", + "Optional, for example http://zaken.intern": "İsteğe bağlı, örneğin http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ofis ağındasın: bu kutucuk iç adresi açar", + "You are not on the office network: this tile opens the main address": "Ofis ağında değilsin: bu kutucuk ana adresi açar" } } diff --git a/l10n/uk.js b/l10n/uk.js index 821218bb4..ec895b309 100644 --- a/l10n/uk.js +++ b/l10n/uk.js @@ -1359,7 +1359,20 @@ OC.L10N.register( "This file could not be read as a bookmarks file." : "Цей файл не вдалося прочитати як файл закладок.", "Room left on this dashboard: {count}. Pick fewer folders." : "Залишилося місця на цій панелі: {count}. Виберіть менше тек.", "The import failed; nothing was added." : "Імпорт не вдався; нічого не додано.", - "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущено {count} закладку: плиткою може стати лише веб-адреса", "Пропущено {count} закладки: плиткою може стати лише веб-адреса", "Пропущено {count} закладок: плиткою може стати лише веб-адреса"] + "_{count} bookmark skipped: only web addresses can become tiles_::_{count} bookmarks skipped: only web addresses can become tiles_" : ["Пропущено {count} закладку: плиткою може стати лише веб-адреса", "Пропущено {count} закладки: плиткою може стати лише веб-адреса", "Пропущено {count} закладок: плиткою може стати лише веб-адреса"], + "Office networks" : "Офісні мережі", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16." : "Запити з цих мереж відкривають офісну адресу плитки, якщо вона є. Введіть по одному діапазону IPv4 або IPv6 у рядку, наприклад 10.20.0.0/16.", + "Network ranges" : "Діапазони мереж", + "Office networks saved." : "Офісні мережі збережено.", + "Save office networks" : "Зберегти офісні мережі", + "Your current address {address} is on the office network." : "Ваша поточна адреса {address} у офісній мережі.", + "Your current address {address} is not on the office network." : "Ваша поточна адреса {address} не в офісній мережі.", + "The office networks could not be loaded." : "Не вдалося завантажити офісні мережі.", + "The office networks could not be saved." : "Не вдалося зберегти офісні мережі.", + "Address on the office network" : "Адреса в офісній мережі", + "Optional, for example http://zaken.intern" : "Необов'язково, наприклад http://zaken.intern", + "You are on the office network: this tile opens the internal address" : "Ви в офісній мережі: ця плитка відкриває внутрішню адресу", + "You are not on the office network: this tile opens the main address" : "Ви не в офісній мережі: ця плитка відкриває основну адресу" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" ); diff --git a/l10n/uk.json b/l10n/uk.json index d18eee5a7..5b83463b2 100644 --- a/l10n/uk.json +++ b/l10n/uk.json @@ -1331,6 +1331,19 @@ "Пропущено {count} закладку: плиткою може стати лише веб-адреса", "Пропущено {count} закладки: плиткою може стати лише веб-адреса", "Пропущено {count} закладок: плиткою може стати лише веб-адреса" - ] + ], + "Office networks": "Офісні мережі", + "Requests from these networks open a tile's address on the office network, when the tile has one. Enter one IPv4 or IPv6 range per line, such as 10.20.0.0/16.": "Запити з цих мереж відкривають офісну адресу плитки, якщо вона є. Введіть по одному діапазону IPv4 або IPv6 у рядку, наприклад 10.20.0.0/16.", + "Network ranges": "Діапазони мереж", + "Office networks saved.": "Офісні мережі збережено.", + "Save office networks": "Зберегти офісні мережі", + "Your current address {address} is on the office network.": "Ваша поточна адреса {address} у офісній мережі.", + "Your current address {address} is not on the office network.": "Ваша поточна адреса {address} не в офісній мережі.", + "The office networks could not be loaded.": "Не вдалося завантажити офісні мережі.", + "The office networks could not be saved.": "Не вдалося зберегти офісні мережі.", + "Address on the office network": "Адреса в офісній мережі", + "Optional, for example http://zaken.intern": "Необов'язково, наприклад http://zaken.intern", + "You are on the office network: this tile opens the internal address": "Ви в офісній мережі: ця плитка відкриває внутрішню адресу", + "You are not on the office network: this tile opens the main address": "Ви не в офісній мережі: ця плитка відкриває основну адресу" } } diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 9886226e9..5e489dcba 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -98,7 +98,7 @@ class PageController extends Controller { * (REQ-SPX-002). * @param OfficeNetworkService $officeNetworks Whether the request comes * from an office network - * (REQ-TIA-002). + * (REQ-TIA-003). */ public function __construct( IRequest $request, diff --git a/lib/Service/InitialStateBuilder.php b/lib/Service/InitialStateBuilder.php index 1a25758cd..a366514f2 100644 --- a/lib/Service/InitialStateBuilder.php +++ b/lib/Service/InitialStateBuilder.php @@ -356,7 +356,7 @@ public function setSearchShortcuts(array $shortcuts): self { /** * Say whether this request comes from an office network, so tiles open - * their internal address (launcher-tile-internal-address, REQ-TIA-002). + * their internal address (launcher-tile-internal-address, REQ-TIA-003). * Optional key; the JS reader defaults it to false. * * @param bool $onOfficeNetwork Whether the request is on an office network. diff --git a/lib/Service/OfficeNetworkService.php b/lib/Service/OfficeNetworkService.php index 5ab632bc3..28a59a4d2 100644 --- a/lib/Service/OfficeNetworkService.php +++ b/lib/Service/OfficeNetworkService.php @@ -102,7 +102,7 @@ public function saveRanges(array $raw): array { } if ($this->isValidRange(range: $range) === false) { - throw new InvalidArgumentException(message: 'Not an IP range: ' . mb_substr(string: $range, start: 0, length: 64)); + throw new InvalidArgumentException(message: 'This is not a valid network range: ' . mb_substr(string: $range, start: 0, length: 64)); } $ranges[$range] = $range; diff --git a/src/components/TileWidget.vue b/src/components/TileWidget.vue index cfd65f44f..419852fd6 100644 --- a/src/components/TileWidget.vue +++ b/src/components/TileWidget.vue @@ -102,6 +102,17 @@ export default { HealthPingBadge, }, + inject: { + /** + * Whether this request comes from an office network (REQ-TIA-003), + * decided by the server from the request address. + */ + injectedOnOfficeNetwork: { + from: 'onOfficeNetwork', + default: false, + }, + }, + props: { tile: { type: Object, @@ -144,13 +155,33 @@ export default { type: Number, default: 60, }, + + /** + * The address to open on the office network (REQ-TIA-003); empty + * keeps the tile on its main address. + * + * @type {string} + */ + internalUrl: { + type: String, + default: '', + }, }, emits: ['edit', 'remove'], computed: { - /** @spec openspec/specs/tiles/spec.md */ + /** + * The address this tile opens: the office network address when the + * server says this request comes from an office network and the tile + * has one, else the main address (REQ-TIA-003). + * + * @spec openspec/specs/tiles/spec.md + */ tileUrl() { + if (this.injectedOnOfficeNetwork === true && this.internalUrl) { + return this.resolveAddress(this.internalUrl) + } const value = this.tile.linkValue if (this.tile.linkType === 'app') { return generateUrl('/apps/' + value) @@ -207,6 +238,21 @@ export default { }, methods: { + /** + * An address as a link: a path on this Nextcloud goes through + * generateUrl, anything else passes through (REQ-TIA-003). + * + * @param {string} value The address. + * @return {string} The href. + * @spec openspec/specs/tiles/spec.md + */ + resolveAddress(value) { + if (value.startsWith('/') && !value.startsWith('//')) { + return generateUrl(value) + } + return value + }, + /** * REQ-TANLT-002 — fire a fire-and-forget tile-click record call * on activation. Never blocks or interferes with the anchor's own diff --git a/src/components/WidgetRenderer.vue b/src/components/WidgetRenderer.vue index bdfc98921..e8ed4d23b 100644 --- a/src/components/WidgetRenderer.vue +++ b/src/components/WidgetRenderer.vue @@ -23,7 +23,8 @@ :tile="tileData" :placementId="placement.id" :healthPingEnabled="normalizedContent.healthPingEnabled === true" - :pingInterval="normalizedContent.pingInterval" /> + :pingInterval="normalizedContent.pingInterval" + :internalUrl="normalizedContent.internalUrl || ''" />