Просмотр исходного кода

Implement sort order per feed (#8234)

* added local feed sorting

Addresses https://github.com/FreshRSS/FreshRSS/issues/4761

- Added number of sorted feeds and associative array for feed sorting option in Context.
- Number of sorted feeds and local sorting option by its index saved into Minz Request Parameters.
- Number of sorted feeds and local sorting options deleted when choosing another Option Of Global Sorting.
- Added option of allowing sorting by feed in configuration.
- Added variable for allowing local sorting in userConf.
- Added function to get feeds by current get in context.
- Added menu button for all individual feed sorting.
- New database options for individual feed sorting in EntryDAO.
- Considered choosing new entries based on chosen load limit.
- Local sorting parameter saved into continuation value in Index Controller.

How to test the feature manually:

1. At the bottom of Reading Configuration menu turn on individual sorting option menu 
2. Choose Sorting by feed option
3. Choose feed at next sorting menu and choose sorting option for that feed

* added feed sorting option

* added sort feeds display

* added template for sort feed name

* added title to feed sorting button

* added comments

* added local sorting option

* Added Docs

* css reset

* added getter and seter for local sort

* added getter and seter for local sort

* allowed sorting per feed

* allowed sorting per feed

* added sorting option for category

* deleted changes from NetryDAO

* add setting up sorting for category

* docs reset

* i18 reset

* updated i18 for category

* added i18 for categories

* added i18 for category

* added setting sorting for feeds and category

* removing userConf.allow-local-sort

* removing userConf.allow-local-sort

* removing white space

* added credits

* removed feeds_by_get

* removed whitespace

* changed escaping for values

* added escaping to user set values

* added in_array

* added secondary sort and order

* added secondary sort and order

* fixed readme

* removed whitespace change

* reseted i18n

* added translations

* added feed setting translations

* fixed i18n

* fixed i18n

* changes in sort order per feed

* changes in sort order per feed

* added secondary sort order

* primary sort

* changed to preferred sort order

* i18n

* Revert wrong whitespace changes

* Re-order new options

* added blank option

* fixed escaping

* fixed default sort in feed

* fixed default sort recovery

* siplyfied option

* added rand option

* Revert unrelated change

* Minor plaintext

* Whitespace and formatting fixes

* Avoid unneeded SQL requests and processing

* Improve syntax

* Improve logic

* Reuse existing translations as much as possible

* i18n

* Remove some options that make little sense

* Separators

* Fix old transation key

* Add help messages

* Progress on secondary sort

* raw name

* Pass parameters. Add TODO

* Progress

* Minor ordering

* Fix parenthesis

---------

Co-authored-by: root <root@LAPTOP-C8TCHHPN.localdomain>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
PeterVavercak 2 месяцев назад
Родитель
Сommit
ee7eb67f3c
74 измененных файлов с 645 добавлено и 290 удалено
  1. 1 0
      CREDITS.md
  2. 8 8
      README.fr.md
  3. 8 8
      README.md
  4. 16 0
      app/Controllers/categoryController.php
  5. 30 2
      app/Controllers/configureController.php
  6. 15 5
      app/Controllers/indexController.php
  7. 16 0
      app/Controllers/subscriptionController.php
  8. 7 0
      app/Models/Category.php
  9. 44 4
      app/Models/Context.php
  10. 96 30
      app/Models/EntryDAO.php
  11. 7 0
      app/Models/Feed.php
  12. 3 1
      app/Models/UserConfiguration.php
  13. 0 5
      app/i18n/cs/conf.php
  14. 10 3
      app/i18n/cs/index.php
  15. 0 5
      app/i18n/de/conf.php
  16. 10 3
      app/i18n/de/index.php
  17. 0 5
      app/i18n/el/conf.php
  18. 10 3
      app/i18n/el/index.php
  19. 0 5
      app/i18n/en-US/conf.php
  20. 10 3
      app/i18n/en-US/index.php
  21. 1 1
      app/i18n/en/admin.php
  22. 4 4
      app/i18n/en/api.php
  23. 0 5
      app/i18n/en/conf.php
  24. 10 3
      app/i18n/en/index.php
  25. 1 1
      app/i18n/en/sub.php
  26. 0 5
      app/i18n/es/conf.php
  27. 10 3
      app/i18n/es/index.php
  28. 0 5
      app/i18n/fa/conf.php
  29. 10 3
      app/i18n/fa/index.php
  30. 0 5
      app/i18n/fi/conf.php
  31. 10 3
      app/i18n/fi/index.php
  32. 0 5
      app/i18n/fr/conf.php
  33. 10 3
      app/i18n/fr/index.php
  34. 0 5
      app/i18n/he/conf.php
  35. 10 3
      app/i18n/he/index.php
  36. 0 5
      app/i18n/hu/conf.php
  37. 10 3
      app/i18n/hu/index.php
  38. 0 5
      app/i18n/id/conf.php
  39. 10 3
      app/i18n/id/index.php
  40. 0 5
      app/i18n/it/conf.php
  41. 10 3
      app/i18n/it/index.php
  42. 0 5
      app/i18n/ja/conf.php
  43. 10 3
      app/i18n/ja/index.php
  44. 0 5
      app/i18n/ko/conf.php
  45. 10 3
      app/i18n/ko/index.php
  46. 0 5
      app/i18n/lv/conf.php
  47. 10 3
      app/i18n/lv/index.php
  48. 0 5
      app/i18n/nl/conf.php
  49. 10 3
      app/i18n/nl/index.php
  50. 0 5
      app/i18n/oc/conf.php
  51. 10 3
      app/i18n/oc/index.php
  52. 0 5
      app/i18n/pl/conf.php
  53. 10 3
      app/i18n/pl/index.php
  54. 0 5
      app/i18n/pt-BR/conf.php
  55. 10 3
      app/i18n/pt-BR/index.php
  56. 0 5
      app/i18n/pt-PT/conf.php
  57. 10 3
      app/i18n/pt-PT/index.php
  58. 0 5
      app/i18n/ru/conf.php
  59. 10 3
      app/i18n/ru/index.php
  60. 0 5
      app/i18n/sk/conf.php
  61. 10 3
      app/i18n/sk/index.php
  62. 0 5
      app/i18n/tr/conf.php
  63. 10 3
      app/i18n/tr/index.php
  64. 0 5
      app/i18n/uk/conf.php
  65. 10 3
      app/i18n/uk/index.php
  66. 0 5
      app/i18n/zh-CN/conf.php
  67. 10 3
      app/i18n/zh-CN/index.php
  68. 0 5
      app/i18n/zh-TW/conf.php
  69. 10 3
      app/i18n/zh-TW/index.php
  70. 3 3
      app/layout/nav_menu.phtml
  71. 48 5
      app/views/configure/reading.phtml
  72. 31 0
      app/views/helpers/category/update.phtml
  73. 30 0
      app/views/helpers/feed/update.phtml
  74. 6 2
      config-user.default.php

+ 1 - 0
CREDITS.md

@@ -226,6 +226,7 @@ People are sorted by name so please keep this order.
 * [PedroPMS](https://github.com/PedroPMS): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:PedroPMS)
 * [PedroPMS](https://github.com/PedroPMS): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:PedroPMS)
 * [perrinjerome](https://github.com/perrinjerome): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:perrinjerome)
 * [perrinjerome](https://github.com/perrinjerome): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:perrinjerome)
 * [Peter Stoinov](https://github.com/stoinov): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:stoinov), [Web](https://stoinov.com)
 * [Peter Stoinov](https://github.com/stoinov): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:stoinov), [Web](https://stoinov.com)
+* [Peter Vaverčák](https://github.com/PeterVavercak): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is%3Apr+author%3APeterVavercak)
 * [Petra Lamborn](https://github.com/petraoleum): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:petraoleum), [Web](https://petras.space)
 * [Petra Lamborn](https://github.com/petraoleum): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:petraoleum), [Web](https://petras.space)
 * [Pim Snel](https://github.com/mipmip): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is%3Apr+author%3Amipmip), [Web](https://www.pimsnel.com)
 * [Pim Snel](https://github.com/mipmip): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is%3Apr+author%3Amipmip), [Web](https://www.pimsnel.com)
 * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:plopoyop)
 * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:plopoyop)

+ 8 - 8
README.fr.md

@@ -228,18 +228,18 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
 | Langage | Progression | |
 | Langage | Progression | |
 | - | - | - |
 | - | - | - |
 | Čeština (cs) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Čeština (cs) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Deutsch (de) | ■■■■■■■■■・ 95% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Deutsch (de) | ■■■■■■■■■・ 94% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 38% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 38% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Español (es) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Español (es) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 92% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 92% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Suomi (fi) | ■■■■■■■■■・ 95% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Suomi (fi) | ■■■■■■■■■・ 94% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 42% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 42% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Magyar (hu) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Bahasa Indonesia (id) | ■■■■■■■■■・ 92% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Italiano (it) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Magyar (hu) | ■■■■■■■■■・ 98% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Bahasa Indonesia (id) | ■■■■■■■■■・ 91% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Italiano (it) | ■■■■■■■■■・ 98% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■■・ 90% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■■・ 90% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 77% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 77% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
@@ -247,11 +247,11 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
 | Occitan (oc) | ■■■■■■■・・・ 76% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Occitan (oc) | ■■■■■■■・・・ 76% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Polski (pl) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Polski (pl) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Português (Brasil) (pt-BR) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Português (Brasil) (pt-BR) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Русский (ru) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 82% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Русский (ru) | ■■■■■■■■■・ 98% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 91% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 91% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Українська (uk) | ■■■■■■■■■・ 94% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Українська (uk) | ■■■■■■■■■・ 93% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 83% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 
 

+ 8 - 8
README.md

@@ -124,18 +124,18 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
 | Language | Progress | |
 | Language | Progress | |
 | - | - | - |
 | - | - | - |
 | Čeština (cs) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Čeština (cs) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Deutsch (de) | ■■■■■■■■■・ 95% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Deutsch (de) | ■■■■■■■■■・ 94% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 38% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 38% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Español (es) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Español (es) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 92% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 92% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Suomi (fi) | ■■■■■■■■■・ 95% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Suomi (fi) | ■■■■■■■■■・ 94% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 42% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 42% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Magyar (hu) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Bahasa Indonesia (id) | ■■■■■■■■■・ 92% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Italiano (it) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Magyar (hu) | ■■■■■■■■■・ 98% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Bahasa Indonesia (id) | ■■■■■■■■■・ 91% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Italiano (it) | ■■■■■■■■■・ 98% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■■・ 90% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■■・ 90% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 77% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 77% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
@@ -143,11 +143,11 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
 | Occitan (oc) | ■■■■■■■・・・ 76% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Occitan (oc) | ■■■■■■■・・・ 76% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Polski (pl) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Polski (pl) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Português (Brasil) (pt-BR) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Português (Brasil) (pt-BR) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Русский (ru) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 82% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Русский (ru) | ■■■■■■■■■・ 98% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 91% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 91% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Українська (uk) | ■■■■■■■■■・ 94% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Українська (uk) | ■■■■■■■■■・ 93% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 83% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 
 

+ 16 - 0
app/Controllers/categoryController.php

@@ -150,6 +150,22 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
 				$category->_attribute('opml_url', null);
 				$category->_attribute('opml_url', null);
 			}
 			}
 
 
+			$defaultSortOrder = Minz_Request::paramString('defaultSortOrder', plaintext: true);
+			if (str_ends_with($defaultSortOrder, '_asc')) {
+				$category->_attribute('defaultOrder', 'ASC');
+				$defaultSortOrder = substr($defaultSortOrder, 0, -strlen('_asc'));
+			} elseif (str_ends_with($defaultSortOrder, '_desc')) {
+				$category->_attribute('defaultOrder', 'DESC');
+				$defaultSortOrder = substr($defaultSortOrder, 0, -strlen('_desc'));
+			} else {
+				$category->_attribute('defaultOrder');
+			}
+			if (in_array($defaultSortOrder, ['id', 'date', 'link', 'title', 'length', 'f.name', 'rand'], true)) {
+				$category->_attribute('defaultSort', $defaultSortOrder);
+			} else {
+				$category->_attribute('defaultSort');
+			}
+
 			$values = [
 			$values = [
 				'kind' => $category->kind(),
 				'kind' => $category->kind(),
 				'name' => Minz_Request::paramString('name'),
 				'name' => Minz_Request::paramString('name'),

+ 30 - 2
app/Controllers/configureController.php

@@ -149,11 +149,39 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
 			FreshRSS_Context::userConf()->reading_confirm = Minz_Request::paramBoolean('reading_confirm');
 			FreshRSS_Context::userConf()->reading_confirm = Minz_Request::paramBoolean('reading_confirm');
 			FreshRSS_Context::userConf()->auto_remove_article = Minz_Request::paramBoolean('auto_remove_article');
 			FreshRSS_Context::userConf()->auto_remove_article = Minz_Request::paramBoolean('auto_remove_article');
 			FreshRSS_Context::userConf()->mark_updated_article_unread = Minz_Request::paramBoolean('mark_updated_article_unread');
 			FreshRSS_Context::userConf()->mark_updated_article_unread = Minz_Request::paramBoolean('mark_updated_article_unread');
-			if (in_array(Minz_Request::paramString('sort_order'), ['ASC', 'DESC'], true)) {
-				FreshRSS_Context::userConf()->sort_order = Minz_Request::paramString('sort_order');
+
+			$sorting = Minz_Request::paramString('primary_sort', plaintext: true);
+			if (str_ends_with($sorting, '_asc')) {
+				FreshRSS_Context::userConf()->sort_order = 'ASC';
+				$sorting = substr($sorting, 0, -strlen('_asc'));
+			} elseif (str_ends_with($sorting, '_desc')) {
+				FreshRSS_Context::userConf()->sort_order = 'DESC';
+				$sorting = substr($sorting, 0, -strlen('_desc'));
 			} else {
 			} else {
 				FreshRSS_Context::userConf()->sort_order = 'DESC';
 				FreshRSS_Context::userConf()->sort_order = 'DESC';
 			}
 			}
+			if (in_array($sorting, ['id', 'c.name', 'date', 'f.name', 'length', 'link', 'title', 'rand'], true)) {
+				FreshRSS_Context::userConf()->sort = $sorting;
+			} else {
+				FreshRSS_Context::userConf()->sort = 'id';
+			}
+
+			$sorting = Minz_Request::paramString('secondary_sort', plaintext: true);
+			if (str_ends_with($sorting, '_asc')) {
+				FreshRSS_Context::userConf()->secondary_sort_order = 'ASC';
+				$sorting = substr($sorting, 0, -strlen('_asc'));
+			} elseif (str_ends_with($sorting, '_desc')) {
+				FreshRSS_Context::userConf()->secondary_sort_order = 'DESC';
+				$sorting = substr($sorting, 0, -strlen('_desc'));
+			} else {
+				FreshRSS_Context::userConf()->secondary_sort_order = 'DESC';
+			}
+			if (in_array($sorting, ['id', 'date', 'link', 'title'], true)) {
+				FreshRSS_Context::userConf()->secondary_sort = $sorting;
+			} else {
+				FreshRSS_Context::userConf()->secondary_sort = 'id';
+			}
+
 			FreshRSS_Context::userConf()->mark_when = [
 			FreshRSS_Context::userConf()->mark_when = [
 				'article' => Minz_Request::paramBoolean('mark_open_article'),
 				'article' => Minz_Request::paramBoolean('mark_open_article'),
 				'gone' => Minz_Request::paramBoolean('read_upon_gone'),
 				'gone' => Minz_Request::paramBoolean('read_upon_gone'),

+ 15 - 5
app/Controllers/indexController.php

@@ -367,15 +367,24 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
 					'c.name' => $pagingEntry->feed()?->categoryId() === FreshRSS_CategoryDAO::DEFAULTCATEGORYID ?
 					'c.name' => $pagingEntry->feed()?->categoryId() === FreshRSS_CategoryDAO::DEFAULTCATEGORYID ?
 						FreshRSS_CategoryDAO::DEFAULT_CATEGORY_NAME : $pagingEntry->feed()?->category()?->name() ?? '',
 						FreshRSS_CategoryDAO::DEFAULT_CATEGORY_NAME : $pagingEntry->feed()?->category()?->name() ?? '',
 					'date' => $pagingEntry->date(raw: true),
 					'date' => $pagingEntry->date(raw: true),
-					'f.name' => $pagingEntry->feed()?->name() ?? '',
+					'f.name' => $pagingEntry->feed()?->name(raw: true) ?? '',
 					'link' => $pagingEntry->link(raw: true),
 					'link' => $pagingEntry->link(raw: true),
 					'title' => $pagingEntry->title(),
 					'title' => $pagingEntry->title(),
 					'lastUserModified' => $pagingEntry->lastUserModified(),
 					'lastUserModified' => $pagingEntry->lastUserModified(),
 					'length' => $pagingEntry->sqlContentLength() ?? 0,
 					'length' => $pagingEntry->sqlContentLength() ?? 0,
 				};
 				};
-				if ($pagingEntry !== null && FreshRSS_Context::$sort === 'c.name') {
-					// Secondary sort criterion
-					$continuation_values[] = $pagingEntry->feed()?->name() ?? '';
+				if (FreshRSS_Context::$sort === 'c.name') {
+					// Internal secondary sort criterion for category name
+					$continuation_values[] = $pagingEntry?->feed()?->name(raw: true) ?? '';
+				}
+				if (in_array(FreshRSS_Context::$sort, ['c.name', 'f.name'], true)) {
+					// User secondary sort criterion
+					$continuation_values[] = $pagingEntry === null ? 0 : match (FreshRSS_Context::$secondary_sort) {
+						'id' => $pagingEntry->id(),
+						'date' => $pagingEntry->date(raw: true),
+						'link' => $pagingEntry->link(raw: true),
+						'title' => $pagingEntry->title(),
+					};
 				}
 				}
 			} elseif (FreshRSS_Context::$sort === 'rand') {
 			} elseif (FreshRSS_Context::$sort === 'rand') {
 				FreshRSS_Context::$continuation_id = '0';
 				FreshRSS_Context::$continuation_id = '0';
@@ -386,7 +395,8 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
 					$type, $id, FreshRSS_Context::$state, FreshRSS_Context::$search,
 					$type, $id, FreshRSS_Context::$state, FreshRSS_Context::$search,
 					id_min: $id_min, id_max: FreshRSS_Context::$id_max, sort: FreshRSS_Context::$sort, order: FreshRSS_Context::$order,
 					id_min: $id_min, id_max: FreshRSS_Context::$id_max, sort: FreshRSS_Context::$sort, order: FreshRSS_Context::$order,
 					continuation_id: FreshRSS_Context::$continuation_id, continuation_values: $continuation_values,
 					continuation_id: FreshRSS_Context::$continuation_id, continuation_values: $continuation_values,
-					limit: $postsPerPage ?? FreshRSS_Context::$number, offset: FreshRSS_Context::$offset) as $entry) {
+					limit: $postsPerPage ?? FreshRSS_Context::$number, offset: FreshRSS_Context::$offset,
+					secondary_sort: FreshRSS_Context::$secondary_sort, secondary_sort_order: FreshRSS_Context::$secondary_sort_order) as $entry) {
 			yield $entry;
 			yield $entry;
 		}
 		}
 	}
 	}

+ 16 - 0
app/Controllers/subscriptionController.php

@@ -334,6 +334,22 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 				$feed->resetCustomFavicon();
 				$feed->resetCustomFavicon();
 			}
 			}
 
 
+			$defaultSortOrder = Minz_Request::paramString('defaultSortOrder', plaintext: true);
+			if (str_ends_with($defaultSortOrder, '_asc')) {
+				$feed->_attribute('defaultOrder', 'ASC');
+				$defaultSortOrder = substr($defaultSortOrder, 0, -strlen('_asc'));
+			} elseif (str_ends_with($defaultSortOrder, '_desc')) {
+				$feed->_attribute('defaultOrder', 'DESC');
+				$defaultSortOrder = substr($defaultSortOrder, 0, -strlen('_desc'));
+			} else {
+				$feed->_attribute('defaultOrder');
+			}
+			if (in_array($defaultSortOrder, ['id', 'date', 'link', 'title', 'length', 'rand'], true)) {
+				$feed->_attribute('defaultSort', $defaultSortOrder);
+			} else {
+				$feed->_attribute('defaultSort');
+			}
+
 			$values = [
 			$values = [
 				'name' => Minz_Request::paramString('name'),
 				'name' => Minz_Request::paramString('name'),
 				'kind' => $feed->kind(),
 				'kind' => $feed->kind(),

+ 7 - 0
app/Models/Category.php

@@ -171,6 +171,13 @@ class FreshRSS_Category extends Minz_Model {
 		$this->sortFeeds();
 		$this->sortFeeds();
 	}
 	}
 
 
+	public function defaultSort(): ?string {
+		return $this->attributeString('defaultSort');
+	}
+	public function defaultOrder(): ?string {
+		return $this->attributeString('defaultOrder');
+	}
+
 	/**
 	/**
 	 * To manually add feeds to this category (not committing to database).
 	 * To manually add feeds to this category (not committing to database).
 	 */
 	 */

+ 44 - 4
app/Models/Context.php

@@ -42,8 +42,12 @@ final class FreshRSS_Context {
 	public static int $state = 0;
 	public static int $state = 0;
 	/** @var 'ASC'|'DESC' */
 	/** @var 'ASC'|'DESC' */
 	public static string $order = 'DESC';
 	public static string $order = 'DESC';
-	/** @var 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length' */
+	/** @var 'id'|'c.name'|'date'|'f.name'|'lastUserModified'|'length'|'link'|'rand'|'title' */
 	public static string $sort = 'id';
 	public static string $sort = 'id';
+	/** @var 'ASC'|'DESC' */
+	public static string $secondary_sort_order = 'DESC';
+	/** @var 'id'|'date'|'link'|'title' */
+	public static string $secondary_sort = 'id';
 	public static int $number = 0;
 	public static int $number = 0;
 	public static int $offset = 0;
 	public static int $offset = 0;
 	public static FreshRSS_BooleanSearch $search;
 	public static FreshRSS_BooleanSearch $search;
@@ -258,10 +262,46 @@ final class FreshRSS_Context {
 		}
 		}
 
 
 		self::$search = new FreshRSS_BooleanSearch(Minz_Request::paramString('search', plaintext: true));
 		self::$search = new FreshRSS_BooleanSearch(Minz_Request::paramString('search', plaintext: true));
-		$order = Minz_Request::paramString('order', plaintext: true) ?: FreshRSS_Context::userConf()->sort_order;
+
+		$default_order = null;
+		$default_sort = null;
+		if (Minz_Request::paramString('order', plaintext: true) === '' || Minz_Request::paramString('sort', plaintext: true) === '') {
+			if (!empty(self::$current_get['feed'])) {
+				$id = self::$current_get['feed'];
+				// We most likely already have the feed object in cache
+				$feed = FreshRSS_Category::findFeed(FreshRSS_Context::categories(), $id);
+				if ($feed === null) {
+					$feedDAO = FreshRSS_Factory::createFeedDao();
+					$feed = $feedDAO->searchById($id);
+				}
+				$default_order = $feed?->defaultOrder();
+				$default_sort = $feed?->defaultSort();
+			} elseif (!empty(self::$current_get['category'])) {
+				$id = self::$current_get['category'];
+				// We most likely already have the category object in cache
+				$category = FreshRSS_Context::categories()[$id] ?? null;
+				if ($category === null) {
+					$categoryDAO = FreshRSS_Factory::createCategoryDao();
+					$category = $categoryDAO->searchById($id);
+				}
+				$default_order = $category?->defaultOrder();
+				$default_sort = $category?->defaultSort();
+			}
+		}
+		$order = Minz_Request::paramString('order', plaintext: true) ?: $default_order ?: FreshRSS_Context::userConf()->sort_order;
 		self::$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
 		self::$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
-		$sort = Minz_Request::paramString('sort', plaintext: true) ?: FreshRSS_Context::userConf()->sort;
-		self::$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified', 'length'], true) ? $sort : 'id';
+		$sort = Minz_Request::paramString('sort', plaintext: true) ?: $default_sort ?: FreshRSS_Context::userConf()->sort;
+		self::$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'lastUserModified', 'length', 'link', 'title', 'rand'], true) ? $sort : 'id';
+
+		if (in_array(self::$sort, ['c.name', 'f.name'], true)) {
+			self::$secondary_sort = FreshRSS_Context::userConf()->secondary_sort;
+			self::$secondary_sort_order = FreshRSS_Context::userConf()->secondary_sort_order;
+			if ($order !== ($default_order ?: FreshRSS_Context::userConf()->sort_order)) {
+				// User swapped order so swap secondary order as well
+				self::$secondary_sort_order = self::$secondary_sort_order === 'DESC' ? 'ASC' : 'DESC';
+			}
+		}
+
 		self::$number = Minz_Request::paramInt('nb') ?: FreshRSS_Context::userConf()->posts_per_page;
 		self::$number = Minz_Request::paramInt('nb') ?: FreshRSS_Context::userConf()->posts_per_page;
 		if (self::$number > FreshRSS_Context::userConf()->max_posts_per_rss) {
 		if (self::$number > FreshRSS_Context::userConf()->max_posts_per_rss) {
 			self::$number = max(
 			self::$number = max(

+ 96 - 30
app/Models/EntryDAO.php

@@ -1270,15 +1270,18 @@ SQL;
 	/**
 	/**
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_max
 	 * @param numeric-string $id_max
-	 * @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length' $sort
+	 * @param 'id'|'c.name'|'date'|'f.name'|'lastUserModified'|'length'|'link'|'rand'|'title' $sort
 	 * @param 'ASC'|'DESC' $order
 	 * @param 'ASC'|'DESC' $order
 	 * @param numeric-string $continuation_id
 	 * @param numeric-string $continuation_id
 	 * @param list<string|int> $continuation_values
 	 * @param list<string|int> $continuation_values
+	 * @param 'id'|'date'|'link'|'title' $secondary_sort
+	 * @param 'ASC'|'DESC' $secondary_sort_order
 	 * @return array{0:list<int|string>,1:string}
 	 * @return array{0:list<int|string>,1:string}
 	 */
 	 */
 	protected function sqlListEntriesWhere(string $alias = '', int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 	protected function sqlListEntriesWhere(string $alias = '', int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 		string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
 		string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
-		string $continuation_id = '0', array $continuation_values = []): array {
+		string $continuation_id = '0', array $continuation_values = [],
+		string $secondary_sort = 'id', string $secondary_sort_order = 'DESC'): array {
 		$search = ' ';
 		$search = ' ';
 		$values = [];
 		$values = [];
 		if ($state & FreshRSS_Entry::STATE_ANDS) {
 		if ($state & FreshRSS_Entry::STATE_ANDS) {
@@ -1338,29 +1341,53 @@ SQL;
 			$values[] = $id_min;
 			$values[] = $id_min;
 		}
 		}
 
 
-		if ($continuation_id !== '0' && in_array($sort, ['c.name', 'date', 'f.name', 'link', 'title', 'lastUserModified', 'length'], true)) {
+		if ($continuation_id !== '0' && in_array($sort, ['c.name', 'date', 'f.name', 'lastUserModified', 'length', 'link', 'title'], true)) {
 			$sign = $order === 'ASC' ? '>' : '<';
 			$sign = $order === 'ASC' ? '>' : '<';
+			$sign2 = $secondary_sort_order === 'ASC' ? '>' : '<';
 			$orderBy = match ($sort) {
 			$orderBy = match ($sort) {
 				'c.name' => 'c.name',
 				'c.name' => 'c.name',
+				'date' => $alias . 'date',
 				'f.name' => 'f.name',
 				'f.name' => 'f.name',
 				'lastUserModified' => $alias . '`lastUserModified`',
 				'lastUserModified' => $alias . '`lastUserModified`',
 				'length' => 'LENGTH(' . $alias . (static::isCompressed() ? 'content_bin' : 'content') . ')',
 				'length' => 'LENGTH(' . $alias . (static::isCompressed() ? 'content_bin' : 'content') . ')',
-				default => $alias . $sort,
+				'link' => $alias . 'link',
+				'title' => $alias . 'title',
+			};
+			$orderBy2 = match ($secondary_sort) {
+				'id' => $alias . 'id',
+				'date' => $alias . 'date',
+				'link' => $alias . 'link',
+				'title' => $alias . 'title',
 			};
 			};
 			// Keyset pagination (Compatibility syntax due to poor performance of tuple syntax in MySQL https://bugs.mysql.com/bug.php?id=104128)
 			// Keyset pagination (Compatibility syntax due to poor performance of tuple syntax in MySQL https://bugs.mysql.com/bug.php?id=104128)
 			if ($sort === 'c.name') {
 			if ($sort === 'c.name') {
-				// Includes a secondary sort by feed name
-				$search .= "AND ((c.name {$sign} ?) OR (c.name = ? AND f.name {$sign} ?) OR (c.name = ? AND f.name = ? AND {$alias}id {$sign}= ?)) ";
-				$values[] = $continuation_values[0];
-				$values[] = $continuation_values[0];
-				$values[] = $continuation_values[1];
-				$values[] = $continuation_values[0];
-				$values[] = $continuation_values[1];
+				// Includes the feed-name sort and a user secondary sort
+				$search .= "AND ((c.name {$sign} ?) OR (c.name = ? AND f.name {$sign} ?) OR (c.name = ? AND f.name = ? AND {$orderBy2} {$sign2}= ?) " .
+					"OR (c.name = ? AND f.name = ? AND {$orderBy2} = ? AND {$alias}id {$sign}= ?)) ";
+				$values[] = $continuation_values[0];	// c.name (primary sort)
+				$values[] = $continuation_values[0];	// c.name (primary sort)
+				$values[] = $continuation_values[1];	// f.name (internal secondary sort)
+				$values[] = $continuation_values[0];	// c.name (primary sort)
+				$values[] = $continuation_values[1];	// f.name (internal secondary sort)
+				$values[] = $continuation_values[2];	// secondary sort
+				$values[] = $continuation_values[0];	// c.name (primary sort)
+				$values[] = $continuation_values[1];	// f.name (internal secondary sort)
+				$values[] = $continuation_values[2];	// secondary sort
+				$values[] = $continuation_id;
+			} elseif ($sort === 'f.name') {
+				// Includes the user secondary sort
+				$search .= "AND ((f.name {$sign} ?) OR (f.name = ? AND {$orderBy2} {$sign2} ?) " .
+					"OR (f.name = ? AND {$orderBy2} = ? AND {$alias}id {$sign}= ?)) ";
+				$values[] = $continuation_values[0];	// f.name (primary sort)
+				$values[] = $continuation_values[0];	// f.name (primary sort)
+				$values[] = $continuation_values[1];	// secondary sort
+				$values[] = $continuation_values[0];	// f.name (primary sort)
+				$values[] = $continuation_values[1];	// secondary sort
 				$values[] = $continuation_id;
 				$values[] = $continuation_id;
 			} else {
 			} else {
 				$search .= "AND ({$orderBy} {$sign} ? OR ({$orderBy} = ? AND {$alias}id {$sign}= ?)) ";
 				$search .= "AND ({$orderBy} {$sign} ? OR ({$orderBy} = ? AND {$alias}id {$sign}= ?)) ";
-				$values[] = $continuation_values[0];
-				$values[] = $continuation_values[0];
+				$values[] = $continuation_values[0];	// primary sort
+				$values[] = $continuation_values[0];	// primary sort
 				$values[] = $continuation_id;
 				$values[] = $continuation_id;
 			}
 			}
 		}
 		}
@@ -1382,16 +1409,19 @@ SQL;
 	 * @param int $id category/feed/tag ID
 	 * @param int $id category/feed/tag ID
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_max
 	 * @param numeric-string $id_max
-	 * @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length' $sort
+	 * @param 'id'|'c.name'|'date'|'f.name'|'lastUserModified'|'length'|'link'|'rand'|'title' $sort
 	 * @param 'ASC'|'DESC' $order
 	 * @param 'ASC'|'DESC' $order
 	 * @param numeric-string $continuation_id
 	 * @param numeric-string $continuation_id
 	 * @param list<string|int> $continuation_values
 	 * @param list<string|int> $continuation_values
+	 * @param 'id'|'date'|'link'|'title' $secondary_sort
+	 * @param 'ASC'|'DESC' $secondary_sort_order
 	 * @return array{0:list<int|string>,1:string}
 	 * @return array{0:list<int|string>,1:string}
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 */
 	 */
 	private function sqlListWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 	private function sqlListWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 			string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
 			string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
-			string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0): array {
+			string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0,
+			string $secondary_sort = 'id', string $secondary_sort_order = 'DESC'): array {
 		if (!$state) {
 		if (!$state) {
 			$state = FreshRSS_Entry::STATE_ALL;
 			$state = FreshRSS_Entry::STATE_ALL;
 		}
 		}
@@ -1441,17 +1471,27 @@ SQL;
 		}
 		}
 
 
 		$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
 		$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
-		$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified', 'length'], true) ? $sort : 'id';
+		$order2 = in_array($secondary_sort_order, ['ASC', 'DESC'], true) ? $secondary_sort_order : 'DESC';
 		$orderBy = match ($sort) {
 		$orderBy = match ($sort) {
+			'id' => 'e.id',
 			'c.name' => 'c.name',
 			'c.name' => 'c.name',
+			'date' => 'e.date',
 			'f.name' => 'f.name',
 			'f.name' => 'f.name',
 			'lastUserModified' => 'e.`lastUserModified`',
 			'lastUserModified' => 'e.`lastUserModified`',
 			'length' => 'LENGTH(e.' . (static::isCompressed() ? 'content_bin' : 'content') . ')',
 			'length' => 'LENGTH(e.' . (static::isCompressed() ? 'content_bin' : 'content') . ')',
+			'link' => 'e.link',
+			'title' => 'e.title',
 			'rand' => static::sqlRandom(),
 			'rand' => static::sqlRandom(),
-			default => 'e.' . $sort,
+		};
+		$orderBy2 = match ($secondary_sort) {
+			'id' => 'e.id',
+			'date' => 'e.date',
+			'link' => 'e.link',
+			'title' => 'e.title',
 		};
 		};
 		[$searchValues, $search] = $this->sqlListEntriesWhere(alias: 'e.', state: $state, filters: $filters, id_min: $id_min, id_max: $id_max,
 		[$searchValues, $search] = $this->sqlListEntriesWhere(alias: 'e.', state: $state, filters: $filters, id_min: $id_min, id_max: $id_max,
-			sort: $sort, order: $order, continuation_id: $continuation_id, continuation_values: $continuation_values);
+			sort: $sort, order: $order, continuation_id: $continuation_id, continuation_values: $continuation_values,
+			secondary_sort: $secondary_sort, secondary_sort_order: $secondary_sort_order);
 
 
 		// Help MySQL/MariaDB's optimizer with the query plan:
 		// Help MySQL/MariaDB's optimizer with the query plan:
 		$useEntryIndex = ($this->pdo->dbType() === 'mysql' &&	// Only relevant for MySQL/MariaDB,
 		$useEntryIndex = ($this->pdo->dbType() === 'mysql' &&	// Only relevant for MySQL/MariaDB,
@@ -1470,7 +1510,8 @@ SQL;
 			. 'WHERE ' . $where
 			. 'WHERE ' . $where
 			. $search
 			. $search
 			. 'ORDER BY ' . $orderBy . ' ' . $order
 			. 'ORDER BY ' . $orderBy . ' ' . $order
-			. ($sort === 'c.name' ? ', f.name ' . $order : '')	// Secondary sort
+			. ($sort === 'c.name' ? ', f.name ' . $order : '')	// Internal secondary sort
+			. (in_array($sort, ['c.name', 'f.name'], true) ? ', ' . $orderBy2 . ' ' . $order2 : '')	// User secondary sort
 			. ($sort === 'id' ? '' : ', e.id ' . $order)	// For keyset pagination
 			. ($sort === 'id' ? '' : ', e.id ' . $order)	// For keyset pagination
 			. ($limit > 0 ? ' LIMIT ' . $limit : '')	// http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
 			. ($limit > 0 ? ' LIMIT ' . $limit : '')	// http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
 			. ($offset > 0 ? ' OFFSET ' . $offset : '')
 			. ($offset > 0 ? ' OFFSET ' . $offset : '')
@@ -1482,28 +1523,41 @@ SQL;
 	 * @param int $id category/feed/tag ID
 	 * @param int $id category/feed/tag ID
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_max
 	 * @param numeric-string $id_max
-	 * @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length' $sort
+	 * @param 'id'|'c.name'|'date'|'f.name'|'lastUserModified'|'length'|'link'|'rand'|'title' $sort
 	 * @param 'ASC'|'DESC' $order
 	 * @param 'ASC'|'DESC' $order
 	 * @param numeric-string $continuation_id
 	 * @param numeric-string $continuation_id
 	 * @param list<string|int> $continuation_values
 	 * @param list<string|int> $continuation_values
+	 * @param 'id'|'date'|'link'|'title' $secondary_sort
+	 * @param 'ASC'|'DESC' $secondary_sort_order
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 */
 	 */
 	private function listWhereRaw(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 	private function listWhereRaw(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 		string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
 		string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
-		string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0): PDOStatement|false {
+		string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0,
+		string $secondary_sort = 'id', string $secondary_sort_order = 'DESC'): PDOStatement|false {
 		$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
 		$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
-		$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified', 'length'], true) ? $sort : 'id';
+		$secondary_sort_order = in_array($secondary_sort_order, ['ASC', 'DESC'], true) ? $secondary_sort_order : 'DESC';
 
 
 		[$values, $sql] = $this->sqlListWhere($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, sort: $sort, order: $order,
 		[$values, $sql] = $this->sqlListWhere($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, sort: $sort, order: $order,
-			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
+			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset,
+			secondary_sort: $secondary_sort, secondary_sort_order: $secondary_sort_order);
 
 
 		$orderBy = match ($sort) {
 		$orderBy = match ($sort) {
+			'id' => 'e0.id',
 			'c.name' => 'c0.name',
 			'c.name' => 'c0.name',
+			'date' => 'e0.date',
 			'f.name' => 'f0.name',
 			'f.name' => 'f0.name',
 			'lastUserModified' => 'e0.`lastUserModified`',
 			'lastUserModified' => 'e0.`lastUserModified`',
 			'length' => 'LENGTH(e0.' . (static::isCompressed() ? 'content_bin' : 'content') . ')',
 			'length' => 'LENGTH(e0.' . (static::isCompressed() ? 'content_bin' : 'content') . ')',
+			'link' => 'e0.link',
+			'title' => 'e0.title',
 			'rand' => static::sqlRandom(),
 			'rand' => static::sqlRandom(),
-			default => 'e0.' . $sort,
+		};
+		$orderBy2 = match ($secondary_sort) {
+			'id' => 'e0.id',
+			'date' => 'e0.date',
+			'link' => 'e0.link',
+			'title' => 'e0.title',
 		};
 		};
 		$content = static::isCompressed() ? 'UNCOMPRESS(e0.content_bin) AS content' : 'e0.content';
 		$content = static::isCompressed() ? 'UNCOMPRESS(e0.content_bin) AS content' : 'e0.content';
 		$hash = static::sqlHexEncode('e0.hash');
 		$hash = static::sqlHexEncode('e0.hash');
@@ -1520,7 +1574,10 @@ SQL;
 		}
 		}
 		$sql .= ' ORDER BY ' . $orderBy . ' ' . $order;
 		$sql .= ' ORDER BY ' . $orderBy . ' ' . $order;
 		if ($sort === 'c.name') {
 		if ($sort === 'c.name') {
-			$sql .= ', f0.name ' . $order;	// Secondary sort
+			$sql .= ', f0.name ' . $order;	// Internal secondary sort
+		}
+		if (in_array($sort, ['c.name', 'f.name'], true)) {
+			$sql .= ', ' . $orderBy2 . ' ' . $secondary_sort_order;	// User secondary sort
 		}
 		}
 		if ($sort !== 'id') {
 		if ($sort !== 'id') {
 			// For keyset pagination
 			// For keyset pagination
@@ -1537,6 +1594,7 @@ SQL;
 					continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
 					continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
 			}
 			}
 			Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
 			Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+			Minz_Log::error('SQL error ' . __METHOD__ . json_encode($sql));
 			return false;
 			return false;
 		}
 		}
 	}
 	}
@@ -1546,18 +1604,22 @@ SQL;
 	 * @param int $id category/feed/tag ID
 	 * @param int $id category/feed/tag ID
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_min
 	 * @param numeric-string $id_max
 	 * @param numeric-string $id_max
-	 * @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length' $sort
+	 * @param 'id'|'c.name'|'date'|'f.name'|'lastUserModified'|'length'|'link'|'rand'|'title' $sort
 	 * @param 'ASC'|'DESC' $order
 	 * @param 'ASC'|'DESC' $order
 	 * @param numeric-string $continuation_id
 	 * @param numeric-string $continuation_id
 	 * @param list<string|int> $continuation_values
 	 * @param list<string|int> $continuation_values
+	 * @param 'id'|'date'|'link'|'title' $secondary_sort
+	 * @param 'ASC'|'DESC' $secondary_sort_order
 	 * @return Traversable<FreshRSS_Entry>
 	 * @return Traversable<FreshRSS_Entry>
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 */
 	 */
 	public function listWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 	public function listWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 			string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
 			string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
-			string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0): Traversable {
+			string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0,
+			string $secondary_sort = 'id', string $secondary_sort_order = 'DESC'): Traversable {
 		$stm = $this->listWhereRaw($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, sort: $sort, order: $order,
 		$stm = $this->listWhereRaw($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, sort: $sort, order: $order,
-			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
+			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset,
+			secondary_sort: $secondary_sort, secondary_sort_order: $secondary_sort_order);
 		if ($stm !== false) {
 		if ($stm !== false) {
 			while (is_array($row = $stm->fetch(PDO::FETCH_ASSOC))) {
 			while (is_array($row = $stm->fetch(PDO::FETCH_ASSOC))) {
 				/** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
 				/** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
@@ -1618,14 +1680,18 @@ SQL;
 	 * @param 'ASC'|'DESC' $order
 	 * @param 'ASC'|'DESC' $order
 	 * @param numeric-string $continuation_id
 	 * @param numeric-string $continuation_id
 	 * @param list<string|int> $continuation_values
 	 * @param list<string|int> $continuation_values
+	 * @param 'id'|'date'|'link'|'title' $secondary_sort
+	 * @param 'ASC'|'DESC' $secondary_sort_order
 	 * @return list<numeric-string>|null
 	 * @return list<numeric-string>|null
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 * @throws FreshRSS_EntriesGetter_Exception
 	 */
 	 */
 	public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 	public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, ?FreshRSS_BooleanSearch $filters = null,
 		string $id_min = '0', string $id_max = '0', string $order = 'DESC',
 		string $id_min = '0', string $id_max = '0', string $order = 'DESC',
-		string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0): ?array {
+		string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0,
+		string $secondary_sort = 'id', string $secondary_sort_order = 'DESC'): ?array {
 		[$values, $sql] = $this->sqlListWhere($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, order: $order,
 		[$values, $sql] = $this->sqlListWhere($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, order: $order,
-			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
+			continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset,
+			secondary_sort: $secondary_sort, secondary_sort_order: $secondary_sort_order);
 		$stm = $this->pdo->prepare($sql);
 		$stm = $this->pdo->prepare($sql);
 		if ($stm !== false && $stm->execute($values)) {
 		if ($stm !== false && $stm->execute($values)) {
 			/** @var list<int|numeric-string> $res */
 			/** @var list<int|numeric-string> $res */

+ 7 - 0
app/Models/Feed.php

@@ -537,6 +537,13 @@ class FreshRSS_Feed extends Minz_Model {
 		$this->nbEntries = $value;
 		$this->nbEntries = $value;
 	}
 	}
 
 
+	public function defaultSort(): ?string {
+		return $this->attributeString('defaultSort');
+	}
+	public function defaultOrder(): ?string {
+		return $this->attributeString('defaultOrder');
+	}
+
 	/**
 	/**
 	 * @throws Minz_FileNotExistException
 	 * @throws Minz_FileNotExistException
 	 * @throws FreshRSS_Feed_Exception
 	 * @throws FreshRSS_Feed_Exception

+ 3 - 1
app/Models/UserConfiguration.php

@@ -56,7 +56,9 @@ declare(strict_types=1);
  * @property bool $show_nav_buttons
  * @property bool $show_nav_buttons
  * @property 'big'|'small'|'none' $mark_read_button
  * @property 'big'|'small'|'none' $mark_read_button
  * @property 'ASC'|'DESC' $sort_order
  * @property 'ASC'|'DESC' $sort_order
- * @property 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'length' $sort
+ * @property 'id'|'c.name'|'date'|'f.name'|'length'|'link'|'rand'|'title' $sort
+ * @property 'ASC'|'DESC' $secondary_sort_order
+ * @property 'id'|'date'|'link'|'title' $secondary_sort
  * @property array<int,array<string,string>> $sharing
  * @property array<int,array<string,string>> $sharing
  * @property array<string,string> $shortcuts
  * @property array<string,string> $shortcuts
  * @property bool $sides_close_article
  * @property bool $sides_close_article

+ 0 - 5
app/i18n/cs/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Použije se také na popisky',
 		'show_fav_unread_help' => 'Použije se také na popisky',
 		'sides_close_article' => 'Kliknutí mimo oblast textu článku zavře článek',
 		'sides_close_article' => 'Kliknutí mimo oblast textu článku zavře článek',
-		'sort' => array(
-			'_' => 'Pořadí řazení',
-			'newer_first' => 'Nejdříve nejnovější',
-			'older_first' => 'Nejdříve nejstarší',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/cs/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Označit kanál jako přečtený',
 		'mark_feed_read' => 'Označit kanál jako přečtený',
 		'mark_selection_unread' => 'Označit výběr jako nepřečtený',
 		'mark_selection_unread' => 'Označit výběr jako nepřečtený',
 		'mylabels' => 'Mé popisky',
 		'mylabels' => 'Mé popisky',
-		'newer_first' => 'Nejdříve novější',
 		'non-starred' => 'Zobrazit neoblíbené',
 		'non-starred' => 'Zobrazit neoblíbené',
 		'normal_view' => 'Normální zobrazení',
 		'normal_view' => 'Normální zobrazení',
-		'older_first' => 'Nejdříve nejstarší',
 		'queries' => 'Uživatelské dotazy',
 		'queries' => 'Uživatelské dotazy',
 		'read' => 'Zobrazit přečtené',
 		'read' => 'Zobrazit přečtené',
 		'reader_view' => 'Zobrazení pro čtení',
 		'reader_view' => 'Zobrazení pro čtení',
 		'rss_view' => 'Kanál RSS',
 		'rss_view' => 'Kanál RSS',
 		'search_short' => 'Hledat',
 		'search_short' => 'Hledat',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/de/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Auch auf Labels anwenden',
 		'show_fav_unread_help' => 'Auch auf Labels anwenden',
 		'sides_close_article' => 'Klick außerhalb des Artikel-Textes schließt den Artikel',
 		'sides_close_article' => 'Klick außerhalb des Artikel-Textes schließt den Artikel',
-		'sort' => array(
-			'_' => 'Sortierreihenfolge',
-			'newer_first' => 'Neuere zuerst',
-			'older_first' => 'Ältere zuerst',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Markiere einen Artikel als Favoriten…',
 			'when' => 'Markiere einen Artikel als Favoriten…',
 		),
 		),

+ 10 - 3
app/i18n/de/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Feed als gelesen markieren',
 		'mark_feed_read' => 'Feed als gelesen markieren',
 		'mark_selection_unread' => 'Auswahl als ungelesen markieren',
 		'mark_selection_unread' => 'Auswahl als ungelesen markieren',
 		'mylabels' => 'Meine Labels',
 		'mylabels' => 'Meine Labels',
-		'newer_first' => 'Neuere zuerst',
 		'non-starred' => 'Nicht-Favoriten zeigen',
 		'non-starred' => 'Nicht-Favoriten zeigen',
 		'normal_view' => 'Normale Ansicht',
 		'normal_view' => 'Normale Ansicht',
-		'older_first' => 'Ältere zuerst',
 		'queries' => 'Benutzerabfragen',
 		'queries' => 'Benutzerabfragen',
 		'read' => 'Gelesene zeigen',
 		'read' => 'Gelesene zeigen',
 		'reader_view' => 'Lese-Ansicht',
 		'reader_view' => 'Lese-Ansicht',
 		'rss_view' => 'RSS-Feed',
 		'rss_view' => 'RSS-Feed',
 		'search_short' => 'Suchen',
 		'search_short' => 'Suchen',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sortierkriterien',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Kategorie, Feed-Titel A→Z',
 				'name_asc' => 'Kategorie, Feed-Titel A→Z',
 				'name_desc' => 'Kategorie, Feed-Titel Z→A',
 				'name_desc' => 'Kategorie, Feed-Titel Z→A',
 			),
 			),
 			'date_asc' => 'Veröffentlichungsdatum 1→9',
 			'date_asc' => 'Veröffentlichungsdatum 1→9',
 			'date_desc' => 'Veröffentlichungsdatum 9→1',
 			'date_desc' => 'Veröffentlichungsdatum 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed-Titel A→Z',
 				'name_asc' => 'Feed-Titel A→Z',
 				'name_desc' => 'Feed-Titel Z→A',
 				'name_desc' => 'Feed-Titel Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Zufällige Reihenfolge',
 			'rand' => 'Zufällige Reihenfolge',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Titel A→Z',
 			'title_asc' => 'Titel A→Z',
 			'title_desc' => 'Titel Z→A',
 			'title_desc' => 'Titel Z→A',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/el/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Applies also on labels',	// TODO
 		'show_fav_unread_help' => 'Applies also on labels',	// TODO
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO
-		'sort' => array(
-			'_' => 'Sort order',	// TODO
-			'newer_first' => 'Newest first',	// TODO
-			'older_first' => 'Oldest first',	// TODO
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/el/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Mark feed as read',	// TODO
 		'mark_feed_read' => 'Mark feed as read',	// TODO
 		'mark_selection_unread' => 'Mark selection as unread',	// TODO
 		'mark_selection_unread' => 'Mark selection as unread',	// TODO
 		'mylabels' => 'My labels',	// TODO
 		'mylabels' => 'My labels',	// TODO
-		'newer_first' => 'Newer first',	// TODO
 		'non-starred' => 'Show non-favourites',	// TODO
 		'non-starred' => 'Show non-favourites',	// TODO
 		'normal_view' => 'Normal view',	// TODO
 		'normal_view' => 'Normal view',	// TODO
-		'older_first' => 'Oldest first',	// TODO
 		'queries' => 'User queries',	// TODO
 		'queries' => 'User queries',	// TODO
 		'read' => 'Show read',	// TODO
 		'read' => 'Show read',	// TODO
 		'reader_view' => 'Reading view',	// TODO
 		'reader_view' => 'Reading view',	// TODO
 		'rss_view' => 'RSS feed',	// TODO
 		'rss_view' => 'RSS feed',	// TODO
 		'search_short' => 'Search',	// TODO
 		'search_short' => 'Search',	// TODO
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/en-US/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Applies also on labels',	// IGNORE
 		'show_fav_unread_help' => 'Applies also on labels',	// IGNORE
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// IGNORE
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// IGNORE
-		'sort' => array(
-			'_' => 'Sort order',	// IGNORE
-			'newer_first' => 'Newest first',	// IGNORE
-			'older_first' => 'Oldest first',	// IGNORE
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favorite…',
 			'when' => 'Mark an article as favorite…',
 		),
 		),

+ 10 - 3
app/i18n/en-US/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Mark feed as read',	// IGNORE
 		'mark_feed_read' => 'Mark feed as read',	// IGNORE
 		'mark_selection_unread' => 'Mark selection as unread',	// IGNORE
 		'mark_selection_unread' => 'Mark selection as unread',	// IGNORE
 		'mylabels' => 'My labels',	// IGNORE
 		'mylabels' => 'My labels',	// IGNORE
-		'newer_first' => 'Newer first',	// IGNORE
 		'non-starred' => 'Show non-favorites',
 		'non-starred' => 'Show non-favorites',
 		'normal_view' => 'Normal view',	// IGNORE
 		'normal_view' => 'Normal view',	// IGNORE
-		'older_first' => 'Oldest first',	// IGNORE
 		'queries' => 'User queries',	// IGNORE
 		'queries' => 'User queries',	// IGNORE
 		'read' => 'Show read',	// IGNORE
 		'read' => 'Show read',	// IGNORE
 		'reader_view' => 'Reading view',	// IGNORE
 		'reader_view' => 'Reading view',	// IGNORE
 		'rss_view' => 'RSS feed',	// IGNORE
 		'rss_view' => 'RSS feed',	// IGNORE
 		'search_short' => 'Search',	// IGNORE
 		'search_short' => 'Search',	// IGNORE
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// IGNORE
+			'asc' => 'Ascending',	// IGNORE
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// IGNORE
 				'name_asc' => 'Category, feed titles A→Z',	// IGNORE
 				'name_desc' => 'Category, feed titles Z→A',	// IGNORE
 				'name_desc' => 'Category, feed titles Z→A',	// IGNORE
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// IGNORE
 			'date_asc' => 'Publication date 1→9',	// IGNORE
 			'date_desc' => 'Publication date 9→1',	// IGNORE
 			'date_desc' => 'Publication date 9→1',	// IGNORE
+			'desc' => 'Descending',	// IGNORE
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// IGNORE
 				'name_asc' => 'Feed title A→Z',	// IGNORE
 				'name_desc' => 'Feed title Z→A',	// IGNORE
 				'name_desc' => 'Feed title Z→A',	// IGNORE
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// IGNORE
 			'length_desc' => 'Content length 9→1',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// IGNORE
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// IGNORE
+			),
 			'rand' => 'Random order',	// IGNORE
 			'rand' => 'Random order',	// IGNORE
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// IGNORE
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// IGNORE
+			),
 			'title_asc' => 'Title A→Z',	// IGNORE
 			'title_asc' => 'Title A→Z',	// IGNORE
 			'title_desc' => 'Title Z→A',	// IGNORE
 			'title_desc' => 'Title Z→A',	// IGNORE
 			'user_modified_asc' => 'User modified 1→9',	// IGNORE
 			'user_modified_asc' => 'User modified 1→9',	// IGNORE

+ 1 - 1
app/i18n/en/admin.php

@@ -31,7 +31,7 @@ return array(
 		'empty_list' => 'There are no installed extensions',
 		'empty_list' => 'There are no installed extensions',
 		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',
 		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',
 		'enabled' => 'Enabled',
 		'enabled' => 'Enabled',
-		'is_compatible' => 'Is compatible',	// TODO
+		'is_compatible' => 'Is compatible',
 		'latest' => 'Installed',
 		'latest' => 'Installed',
 		'name' => 'Name',
 		'name' => 'Name',
 		'no_configure_view' => 'This extension cannot be configured.',
 		'no_configure_view' => 'This extension cannot be configured.',

+ 4 - 4
app/i18n/en/api.php

@@ -14,10 +14,10 @@ return array(
 	'information' => array(
 	'information' => array(
 		'address' => 'Your API address:',
 		'address' => 'Your API address:',
 		'output' => array(
 		'output' => array(
-			'encoding-support' => '⚠️ WARN: no <code>%2F</code> support, some clients might not work!',	// TODO
-			'invalid-configuration' => '⚠️ WARN: Probable invalid base URL in ./data/config.php',	// TODO
-			'pass' => '✔️ PASS',	// TODO
-			'unknown-error' => '❌ ',	// TODO
+			'encoding-support' => '⚠️ WARN: no <code>%2F</code> support, some clients might not work!',
+			'invalid-configuration' => '⚠️ WARN: Probable invalid base URL in ./data/config.php',
+			'pass' => '✔️ PASS',
+			'unknown-error' => '❌ ',
 		),
 		),
 		'test' => array(
 		'test' => array(
 			'fever' => 'Fever API configuration test:',
 			'fever' => 'Fever API configuration test:',

+ 0 - 5
app/i18n/en/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Applies also on labels',
 		'show_fav_unread_help' => 'Applies also on labels',
 		'sides_close_article' => 'Clicking outside of article text area closes the article',
 		'sides_close_article' => 'Clicking outside of article text area closes the article',
-		'sort' => array(
-			'_' => 'Sort order',
-			'newer_first' => 'Newest first',
-			'older_first' => 'Oldest first',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',
 			'when' => 'Mark an article as favourite…',
 		),
 		),

+ 10 - 3
app/i18n/en/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Mark feed as read',
 		'mark_feed_read' => 'Mark feed as read',
 		'mark_selection_unread' => 'Mark selection as unread',
 		'mark_selection_unread' => 'Mark selection as unread',
 		'mylabels' => 'My labels',
 		'mylabels' => 'My labels',
-		'newer_first' => 'Newer first',
 		'non-starred' => 'Show non-favourites',
 		'non-starred' => 'Show non-favourites',
 		'normal_view' => 'Normal view',
 		'normal_view' => 'Normal view',
-		'older_first' => 'Oldest first',
 		'queries' => 'User queries',
 		'queries' => 'User queries',
 		'read' => 'Show read',
 		'read' => 'Show read',
 		'reader_view' => 'Reading view',
 		'reader_view' => 'Reading view',
 		'rss_view' => 'RSS feed',
 		'rss_view' => 'RSS feed',
 		'search_short' => 'Search',
 		'search_short' => 'Search',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',
+			'asc' => 'Ascending',
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',
 				'name_asc' => 'Category, feed titles A→Z',
 				'name_desc' => 'Category, feed titles Z→A',
 				'name_desc' => 'Category, feed titles Z→A',
 			),
 			),
 			'date_asc' => 'Publication date 1→9',
 			'date_asc' => 'Publication date 1→9',
 			'date_desc' => 'Publication date 9→1',
 			'date_desc' => 'Publication date 9→1',
+			'desc' => 'Descending',
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',
 				'name_asc' => 'Feed title A→Z',
 				'name_desc' => 'Feed title Z→A',
 				'name_desc' => 'Feed title Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',
 			'length_desc' => 'Content length 9→1',
 			'link_asc' => 'Link A→Z',
 			'link_asc' => 'Link A→Z',
 			'link_desc' => 'Link Z→A',
 			'link_desc' => 'Link Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',
+			),
 			'rand' => 'Random order',
 			'rand' => 'Random order',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',
+			),
 			'title_asc' => 'Title A→Z',
 			'title_asc' => 'Title A→Z',
 			'title_desc' => 'Title Z→A',
 			'title_desc' => 'Title Z→A',
 			'user_modified_asc' => 'User modified 1→9',
 			'user_modified_asc' => 'User modified 1→9',

+ 1 - 1
app/i18n/en/sub.php

@@ -211,7 +211,7 @@ return array(
 		'priority' => array(
 		'priority' => array(
 			'_' => 'Visibility',
 			'_' => 'Visibility',
 			'category' => 'Show in its category',
 			'category' => 'Show in its category',
-			'feed' => 'Show in its feed',	// TODO
+			'feed' => 'Show in its feed',
 			'hidden' => 'Do not show',
 			'hidden' => 'Do not show',
 			'important' => 'Show in important feeds',
 			'important' => 'Show in important feeds',
 			'main_stream' => 'Show in main stream',
 			'main_stream' => 'Show in main stream',

+ 0 - 5
app/i18n/es/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Se aplica también en las etiquetas',
 		'show_fav_unread_help' => 'Se aplica también en las etiquetas',
 		'sides_close_article' => 'Pinchar fuera del área de texto del artículo lo cerrará',
 		'sides_close_article' => 'Pinchar fuera del área de texto del artículo lo cerrará',
-		'sort' => array(
-			'_' => 'Orden',
-			'newer_first' => 'Nuevos primero',
-			'older_first' => 'Antiguos primero',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Marca un artículo como favorito…',
 			'when' => 'Marca un artículo como favorito…',
 		),
 		),

+ 10 - 3
app/i18n/es/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Marcar fuente como leída',
 		'mark_feed_read' => 'Marcar fuente como leída',
 		'mark_selection_unread' => 'Marcar la selección como no leída',
 		'mark_selection_unread' => 'Marcar la selección como no leída',
 		'mylabels' => 'Mis etiquetas',
 		'mylabels' => 'Mis etiquetas',
-		'newer_first' => 'Nuevos primero',
 		'non-starred' => 'Mostrar todos menos los favoritos',
 		'non-starred' => 'Mostrar todos menos los favoritos',
 		'normal_view' => 'Vista normal',
 		'normal_view' => 'Vista normal',
-		'older_first' => 'Más antiguos primero',
 		'queries' => 'Búsquedas de usuario',
 		'queries' => 'Búsquedas de usuario',
 		'read' => 'Mostrar solo los leídos',
 		'read' => 'Mostrar solo los leídos',
 		'reader_view' => 'Vista de lectura',
 		'reader_view' => 'Vista de lectura',
 		'rss_view' => 'Fuente RSS',
 		'rss_view' => 'Fuente RSS',
 		'search_short' => 'Buscar',
 		'search_short' => 'Buscar',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Criterios de ordenación',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Categoría, títulos de fuentes A→Z',
 				'name_asc' => 'Categoría, títulos de fuentes A→Z',
 				'name_desc' => 'Categoría, títulos de fuentes Z→A',
 				'name_desc' => 'Categoría, títulos de fuentes Z→A',
 			),
 			),
 			'date_asc' => 'Fecha de publicación 1→9',
 			'date_asc' => 'Fecha de publicación 1→9',
 			'date_desc' => 'Fecha de publicación 9→1',
 			'date_desc' => 'Fecha de publicación 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Título de fuente A→Z',
 				'name_asc' => 'Título de fuente A→Z',
 				'name_desc' => 'Título de fuente Z→A',
 				'name_desc' => 'Título de fuente Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Longitud de contenido 9→1',
 			'length_desc' => 'Longitud de contenido 9→1',
 			'link_asc' => 'Enlace A→Z',
 			'link_asc' => 'Enlace A→Z',
 			'link_desc' => 'Enlace Z→A',
 			'link_desc' => 'Enlace Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Orden aleatorio',
 			'rand' => 'Orden aleatorio',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Título A→Z',
 			'title_asc' => 'Título A→Z',
 			'title_desc' => 'Título Z→A',
 			'title_desc' => 'Título Z→A',
 			'user_modified_asc' => 'Modificado por usuario 1→9',
 			'user_modified_asc' => 'Modificado por usuario 1→9',

+ 0 - 5
app/i18n/fa/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => ' روی برچسب ها نیز اعمال می شود',
 		'show_fav_unread_help' => ' روی برچسب ها نیز اعمال می شود',
 		'sides_close_article' => ' با کلیک کردن خارج از ناحیه متن مقاله',
 		'sides_close_article' => ' با کلیک کردن خارج از ناحیه متن مقاله',
-		'sort' => array(
-			'_' => ' ترتیب مرتب سازی',
-			'newer_first' => ' ابتدا جدیدترین',
-			'older_first' => ' اول قدیمی ترین',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'یک مطلب را به عنوان مورد علاقه علامت‌گذاری کن...',
 			'when' => 'یک مطلب را به عنوان مورد علاقه علامت‌گذاری کن...',
 		),
 		),

+ 10 - 3
app/i18n/fa/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => ' فید را به عنوان خوانده شده علامت گذاری کنید',
 		'mark_feed_read' => ' فید را به عنوان خوانده شده علامت گذاری کنید',
 		'mark_selection_unread' => ' انتخاب را به عنوان خوانده نشده علامت گذاری کنید',
 		'mark_selection_unread' => ' انتخاب را به عنوان خوانده نشده علامت گذاری کنید',
 		'mylabels' => ' برچسب های من',
 		'mylabels' => ' برچسب های من',
-		'newer_first' => ' ابتدا جدیدتر',
 		'non-starred' => ' موارد غیر مورد علاقه را نشان دهید',
 		'non-starred' => ' موارد غیر مورد علاقه را نشان دهید',
 		'normal_view' => ' نمای عادی',
 		'normal_view' => ' نمای عادی',
-		'older_first' => ' اول مسن ترین',
 		'queries' => ' پرس و جوهای کاربر',
 		'queries' => ' پرس و جوهای کاربر',
 		'read' => ' نمایش خوانده شده',
 		'read' => ' نمایش خوانده شده',
 		'reader_view' => ' مشاهده خواندن',
 		'reader_view' => ' مشاهده خواندن',
 		'rss_view' => ' خوراک RSS',
 		'rss_view' => ' خوراک RSS',
 		'search_short' => ' جستجو',
 		'search_short' => ' جستجو',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'معیارهای مرتب‌سازی',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'دسته بندی، عناوین فید A→Z',
 				'name_asc' => 'دسته بندی، عناوین فید A→Z',
 				'name_desc' => 'دسته بندی، عناوین فید Z→A',
 				'name_desc' => 'دسته بندی، عناوین فید Z→A',
 			),
 			),
 			'date_asc' => 'تاریخ انتشار ۱→۹',
 			'date_asc' => 'تاریخ انتشار ۱→۹',
 			'date_desc' => 'تاریخ انتشار ۹→۱',
 			'date_desc' => 'تاریخ انتشار ۹→۱',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'عنوان فید A→Z',
 				'name_asc' => 'عنوان فید A→Z',
 				'name_desc' => 'عنوان فید Z→A',
 				'name_desc' => 'عنوان فید Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'لینک A→Z',
 			'link_asc' => 'لینک A→Z',
 			'link_desc' => 'لینک Z→A',
 			'link_desc' => 'لینک Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'ترتیب تصادفی',
 			'rand' => 'ترتیب تصادفی',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'عنوانA→Z',
 			'title_asc' => 'عنوانA→Z',
 			'title_desc' => 'عنوان Z→A',
 			'title_desc' => 'عنوان Z→A',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/fi/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Koskee myös merkintöjä',
 		'show_fav_unread_help' => 'Koskee myös merkintöjä',
 		'sides_close_article' => 'Artikkeli sulkeutuu napsauttamalla sen ulkopuolelle',
 		'sides_close_article' => 'Artikkeli sulkeutuu napsauttamalla sen ulkopuolelle',
-		'sort' => array(
-			'_' => 'Lajittelujärjestys',
-			'newer_first' => 'Uusimmat ensin',
-			'older_first' => 'Vanhimmat ensin',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Merkitse artikkeli suosikiksi…',
 			'when' => 'Merkitse artikkeli suosikiksi…',
 		),
 		),

+ 10 - 3
app/i18n/fi/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Merkitse syöte luetuksi',
 		'mark_feed_read' => 'Merkitse syöte luetuksi',
 		'mark_selection_unread' => 'Merkitse valitut lukemattomiksi',
 		'mark_selection_unread' => 'Merkitse valitut lukemattomiksi',
 		'mylabels' => 'Omat tunnisteet',
 		'mylabels' => 'Omat tunnisteet',
-		'newer_first' => 'Uusin ensin',
 		'non-starred' => 'Näytä muut kuin suosikit',
 		'non-starred' => 'Näytä muut kuin suosikit',
 		'normal_view' => 'Tavallinen näkymä',
 		'normal_view' => 'Tavallinen näkymä',
-		'older_first' => 'Vanhin ensin',
 		'queries' => 'Käyttäjän tekemät kyselyt',
 		'queries' => 'Käyttäjän tekemät kyselyt',
 		'read' => 'Näytä luetut',
 		'read' => 'Näytä luetut',
 		'reader_view' => 'Lukunäkymä',
 		'reader_view' => 'Lukunäkymä',
 		'rss_view' => 'RSS-syöte',
 		'rss_view' => 'RSS-syöte',
 		'search_short' => 'Haku',
 		'search_short' => 'Haku',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Lajitteluehdot',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Luokka, syötteiden otsikot A→Ö',
 				'name_asc' => 'Luokka, syötteiden otsikot A→Ö',
 				'name_desc' => 'Luokka, syötteiden otsikot Ö→A',
 				'name_desc' => 'Luokka, syötteiden otsikot Ö→A',
 			),
 			),
 			'date_asc' => 'Julkaisupäivä 1→9',
 			'date_asc' => 'Julkaisupäivä 1→9',
 			'date_desc' => 'Julkaisupäivä 9→1',
 			'date_desc' => 'Julkaisupäivä 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Syötteen otsikko A→Ö',
 				'name_asc' => 'Syötteen otsikko A→Ö',
 				'name_desc' => 'Syötteen otsikko Ö→A',
 				'name_desc' => 'Syötteen otsikko Ö→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Linkki A→Ö',
 			'link_asc' => 'Linkki A→Ö',
 			'link_desc' => 'Linkki Ö→A',
 			'link_desc' => 'Linkki Ö→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Satunnainen järjestys',
 			'rand' => 'Satunnainen järjestys',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Otsikko A→Ö',
 			'title_asc' => 'Otsikko A→Ö',
 			'title_desc' => 'Otsikko Ö→A',
 			'title_desc' => 'Otsikko Ö→A',
 			'user_modified_asc' => 'Käyttäjä muokannut 1→9',
 			'user_modified_asc' => 'Käyttäjä muokannut 1→9',

+ 0 - 5
app/i18n/fr/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'S’applique aussi aux étiquettes',
 		'show_fav_unread_help' => 'S’applique aussi aux étiquettes',
 		'sides_close_article' => 'Cliquer hors de la zone de texte ferme l’article',
 		'sides_close_article' => 'Cliquer hors de la zone de texte ferme l’article',
-		'sort' => array(
-			'_' => 'Ordre de tri',
-			'newer_first' => 'Plus récents en premier',
-			'older_first' => 'Plus anciens en premier',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Marquer un article comme favori…',
 			'when' => 'Marquer un article comme favori…',
 		),
 		),

+ 10 - 3
app/i18n/fr/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Marquer le flux comme lu',
 		'mark_feed_read' => 'Marquer le flux comme lu',
 		'mark_selection_unread' => 'Marquer la sélection comme non-lue',
 		'mark_selection_unread' => 'Marquer la sélection comme non-lue',
 		'mylabels' => 'Mes étiquettes',
 		'mylabels' => 'Mes étiquettes',
-		'newer_first' => 'Plus récents en premier',
 		'non-starred' => 'Afficher les non-favoris',
 		'non-starred' => 'Afficher les non-favoris',
 		'normal_view' => 'Vue normale',
 		'normal_view' => 'Vue normale',
-		'older_first' => 'Plus anciens en premier',
 		'queries' => 'Filtres utilisateurs',
 		'queries' => 'Filtres utilisateurs',
 		'read' => 'Afficher les lus',
 		'read' => 'Afficher les lus',
 		'reader_view' => 'Vue lecture',
 		'reader_view' => 'Vue lecture',
 		'rss_view' => 'Flux RSS',
 		'rss_view' => 'Flux RSS',
 		'search_short' => 'Rechercher',
 		'search_short' => 'Rechercher',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Critère de tri',
+			'asc' => 'Ascendant',
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Catégorie, flux (titres) A→Z',
 				'name_asc' => 'Catégorie, flux (titres) A→Z',
 				'name_desc' => 'Catégorie, flux (titres) Z→A',
 				'name_desc' => 'Catégorie, flux (titres) Z→A',
 			),
 			),
 			'date_asc' => 'Date de publication 1→9',
 			'date_asc' => 'Date de publication 1→9',
 			'date_desc' => 'Date de publication 9→1',
 			'date_desc' => 'Date de publication 9→1',
+			'desc' => 'Descendant',
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Flux (titre) A→Z',
 				'name_asc' => 'Flux (titre) A→Z',
 				'name_desc' => 'Flux (titre) Z→A',
 				'name_desc' => 'Flux (titre) Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Longueur du contenu 9→1',
 			'length_desc' => 'Longueur du contenu 9→1',
 			'link_asc' => 'Lien A→Z',
 			'link_asc' => 'Lien A→Z',
 			'link_desc' => 'Lien Z→A',
 			'link_desc' => 'Lien Z→A',
+			'primary' => array(
+				'_' => 'Critère de tri',
+				'help' => 'Le tri par date de <em>réception</em> est recommandé dans la plupart des cas, pour une meilleure cohérence et performance',
+			),
 			'rand' => 'Ordre aléatoire',
 			'rand' => 'Ordre aléatoire',
+			'secondary' => array(
+				'_' => 'Critère de tri secondaire',
+				'help' => 'Seulemement pertinent lorsque le critère de tri principal est sur les titres des catégories ou des flux',
+			),
 			'title_asc' => 'Titre A→Z',
 			'title_asc' => 'Titre A→Z',
 			'title_desc' => 'Titre Z→A',
 			'title_desc' => 'Titre Z→A',
 			'user_modified_asc' => 'Modifié par l’utilisateur 1→9',
 			'user_modified_asc' => 'Modifié par l’utilisateur 1→9',

+ 0 - 5
app/i18n/he/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Applies also on labels',	// TODO
 		'show_fav_unread_help' => 'Applies also on labels',	// TODO
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO
-		'sort' => array(
-			'_' => 'סדר המיון',
-			'newer_first' => 'חדשים בראש',
-			'older_first' => 'ישנים יותר בראש',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/he/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'סימון הזנה כנקראה',
 		'mark_feed_read' => 'סימון הזנה כנקראה',
 		'mark_selection_unread' => 'Mark selection as unread',	// TODO
 		'mark_selection_unread' => 'Mark selection as unread',	// TODO
 		'mylabels' => 'My labels',	// TODO
 		'mylabels' => 'My labels',	// TODO
-		'newer_first' => 'חדשים בראש',
 		'non-starred' => 'הצגת הכל פרט למועדפים',
 		'non-starred' => 'הצגת הכל פרט למועדפים',
 		'normal_view' => 'תצוגה רגילה',
 		'normal_view' => 'תצוגה רגילה',
-		'older_first' => 'ישנים יותר בראש',
 		'queries' => 'שאילתות',
 		'queries' => 'שאילתות',
 		'read' => 'הצגת נקראו בלבד',
 		'read' => 'הצגת נקראו בלבד',
 		'reader_view' => 'תצוגת קריאה',
 		'reader_view' => 'תצוגת קריאה',
 		'rss_view' => 'הזנת RSS',
 		'rss_view' => 'הזנת RSS',
 		'search_short' => 'חיפוש',
 		'search_short' => 'חיפוש',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/hu/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'A címkékre is vonatkozik',
 		'show_fav_unread_help' => 'A címkékre is vonatkozik',
 		'sides_close_article' => 'A cikk szövegrészén kívüli kattintás bezárja a cikket',
 		'sides_close_article' => 'A cikk szövegrészén kívüli kattintás bezárja a cikket',
-		'sort' => array(
-			'_' => 'Rendezési sorrend',
-			'newer_first' => 'Újabb elöl',
-			'older_first' => 'Régebbi elöl',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Cikk megjelölése kedvencnek…',
 			'when' => 'Cikk megjelölése kedvencnek…',
 		),
 		),

+ 10 - 3
app/i18n/hu/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Hírforrás megjelölése olvasottként',
 		'mark_feed_read' => 'Hírforrás megjelölése olvasottként',
 		'mark_selection_unread' => 'Kijelöltek olvasatlanná tétele',
 		'mark_selection_unread' => 'Kijelöltek olvasatlanná tétele',
 		'mylabels' => 'Címkék',
 		'mylabels' => 'Címkék',
-		'newer_first' => 'Újabbak elöl',
 		'non-starred' => 'Nem kedvencek megjelenítése',
 		'non-starred' => 'Nem kedvencek megjelenítése',
 		'normal_view' => 'Normál nézet',
 		'normal_view' => 'Normál nézet',
-		'older_first' => 'Régebbiek elöl',
 		'queries' => 'Felhasználói lekérdezések',
 		'queries' => 'Felhasználói lekérdezések',
 		'read' => 'Olvasottak megjelenítése',
 		'read' => 'Olvasottak megjelenítése',
 		'reader_view' => 'Olvasó nézet',
 		'reader_view' => 'Olvasó nézet',
 		'rss_view' => 'RSS hírforrás',
 		'rss_view' => 'RSS hírforrás',
 		'search_short' => 'Keresés',
 		'search_short' => 'Keresés',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Rendezési sorrend',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Kategória, feed címek A→Z',
 				'name_asc' => 'Kategória, feed címek A→Z',
 				'name_desc' => 'Kategória, feed címek Z→A',
 				'name_desc' => 'Kategória, feed címek Z→A',
 			),
 			),
 			'date_asc' => 'Kiadás dátuma 1→9',
 			'date_asc' => 'Kiadás dátuma 1→9',
 			'date_desc' => 'Kiadás dátuma 9→1',
 			'date_desc' => 'Kiadás dátuma 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed cím A→Z',
 				'name_asc' => 'Feed cím A→Z',
 				'name_desc' => 'Feed cím Z→A',
 				'name_desc' => 'Feed cím Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Tartalom hossza 9→1',
 			'length_desc' => 'Tartalom hossza 9→1',
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Véletlen sorrend',
 			'rand' => 'Véletlen sorrend',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Cím A→Z',
 			'title_asc' => 'Cím A→Z',
 			'title_desc' => 'Cím Z→A',
 			'title_desc' => 'Cím Z→A',
 			'user_modified_asc' => 'Felhasználói módosítás 1→9',
 			'user_modified_asc' => 'Felhasználói módosítás 1→9',

+ 0 - 5
app/i18n/id/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Berlaku juga pada label',
 		'show_fav_unread_help' => 'Berlaku juga pada label',
 		'sides_close_article' => 'Klik di luar area teks artikel untuk menutup artikel',
 		'sides_close_article' => 'Klik di luar area teks artikel untuk menutup artikel',
-		'sort' => array(
-			'_' => 'Kriteria pengurutan',
-			'newer_first' => 'Terbaru dulu',
-			'older_first' => 'Terlama dulu',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Tandai artikel sebagai favorit…',
 			'when' => 'Tandai artikel sebagai favorit…',
 		),
 		),

+ 10 - 3
app/i18n/id/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Tandai umpan sebagai sudah dibaca',
 		'mark_feed_read' => 'Tandai umpan sebagai sudah dibaca',
 		'mark_selection_unread' => 'Tandai yang dipilih sebagai belum dibaca',
 		'mark_selection_unread' => 'Tandai yang dipilih sebagai belum dibaca',
 		'mylabels' => 'Label Saya',
 		'mylabels' => 'Label Saya',
-		'newer_first' => 'Yang terbaru dulu',
 		'non-starred' => 'Tampilkan yang tidak difavoritkan',
 		'non-starred' => 'Tampilkan yang tidak difavoritkan',
 		'normal_view' => 'Tampilan Normal',
 		'normal_view' => 'Tampilan Normal',
-		'older_first' => 'Yang terlama dulu',
 		'queries' => 'Pencarian pengguna',
 		'queries' => 'Pencarian pengguna',
 		'read' => 'Tampilkan yang sudah dibaca',
 		'read' => 'Tampilkan yang sudah dibaca',
 		'reader_view' => 'Tampilan Membaca',
 		'reader_view' => 'Tampilan Membaca',
 		'rss_view' => 'Umpan RSS',
 		'rss_view' => 'Umpan RSS',
 		'search_short' => 'Cari',
 		'search_short' => 'Cari',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Kriteria pengurutan',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Tanggal publikasi 1→9',
 			'date_asc' => 'Tanggal publikasi 1→9',
 			'date_desc' => 'Tanggal publikasi 9→1',
 			'date_desc' => 'Tanggal publikasi 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Tautan A→Z',
 			'link_asc' => 'Tautan A→Z',
 			'link_desc' => 'Tautan Z→A',
 			'link_desc' => 'Tautan Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Acak',
 			'rand' => 'Acak',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Judul A→Z',
 			'title_asc' => 'Judul A→Z',
 			'title_desc' => 'Judul Z→A',
 			'title_desc' => 'Judul Z→A',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/it/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Si applica anche alle etichette',
 		'show_fav_unread_help' => 'Si applica anche alle etichette',
 		'sides_close_article' => 'Cliccare fuori dall’area di testo dell’articolo chiude l’articolo',
 		'sides_close_article' => 'Cliccare fuori dall’area di testo dell’articolo chiude l’articolo',
-		'sort' => array(
-			'_' => 'Ordinamento',
-			'newer_first' => 'Prima i più recenti',
-			'older_first' => 'Prima i più vecchi',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Segna un articolo come preferito…',
 			'when' => 'Segna un articolo come preferito…',
 		),
 		),

+ 10 - 3
app/i18n/it/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Segna il feed come letto',
 		'mark_feed_read' => 'Segna il feed come letto',
 		'mark_selection_unread' => 'Segna i selezionati come non letti',
 		'mark_selection_unread' => 'Segna i selezionati come non letti',
 		'mylabels' => 'Le mie etichette',
 		'mylabels' => 'Le mie etichette',
-		'newer_first' => 'Mostra prima i recenti',
 		'non-starred' => 'Escludi preferiti',
 		'non-starred' => 'Escludi preferiti',
 		'normal_view' => 'Vista elenco',
 		'normal_view' => 'Vista elenco',
-		'older_first' => 'Ordina per meno recenti',
 		'queries' => 'Chiavi di ricerca',
 		'queries' => 'Chiavi di ricerca',
 		'read' => 'Mostra solo letti',
 		'read' => 'Mostra solo letti',
 		'reader_view' => 'Modalità di lettura',
 		'reader_view' => 'Modalità di lettura',
 		'rss_view' => 'Feed RSS',
 		'rss_view' => 'Feed RSS',
 		'search_short' => 'Cerca',
 		'search_short' => 'Cerca',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Ordina per',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Categoria, titolo del feed A→Z',
 				'name_asc' => 'Categoria, titolo del feed A→Z',
 				'name_desc' => 'Categoria, titolo del feed Z→A',
 				'name_desc' => 'Categoria, titolo del feed Z→A',
 			),
 			),
 			'date_asc' => 'Data di pubblicazione 1→9',
 			'date_asc' => 'Data di pubblicazione 1→9',
 			'date_desc' => 'Data di pubblicazione 9→1',
 			'date_desc' => 'Data di pubblicazione 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Titolo del feed A→Z',
 				'name_asc' => 'Titolo del feed A→Z',
 				'name_desc' => 'Titolo del feed Z→A',
 				'name_desc' => 'Titolo del feed Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Lunghezza contenuto 9→1',
 			'length_desc' => 'Lunghezza contenuto 9→1',
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Ordine casuale',
 			'rand' => 'Ordine casuale',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Titolo A→Z',
 			'title_asc' => 'Titolo A→Z',
 			'title_desc' => 'Titolo Z→A',
 			'title_desc' => 'Titolo Z→A',
 			'user_modified_asc' => 'Modificato dall’utente 1→9',
 			'user_modified_asc' => 'Modificato dall’utente 1→9',

+ 0 - 5
app/i18n/ja/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'ラベルも適用する',
 		'show_fav_unread_help' => 'ラベルも適用する',
 		'sides_close_article' => '記事の外をクリックすると記事を閉じるようにする',
 		'sides_close_article' => '記事の外をクリックすると記事を閉じるようにする',
-		'sort' => array(
-			'_' => '順序',
-			'newer_first' => '最新のものを先頭にする',
-			'older_first' => '最古のものを先頭にする',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => '記事をお気に入りに登録する。',
 			'when' => '記事をお気に入りに登録する。',
 		),
 		),

+ 10 - 3
app/i18n/ja/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'フィードを既読にする',
 		'mark_feed_read' => 'フィードを既読にする',
 		'mark_selection_unread' => '選択した記事を未読にする',
 		'mark_selection_unread' => '選択した記事を未読にする',
 		'mylabels' => 'ラベル',
 		'mylabels' => 'ラベル',
-		'newer_first' => '最新の記事を先頭にする',
 		'non-starred' => 'お気に入りに登録されてない記事を表示する',
 		'non-starred' => 'お気に入りに登録されてない記事を表示する',
 		'normal_view' => 'ノーマルビュー',
 		'normal_view' => 'ノーマルビュー',
-		'older_first' => '最古の記事を先頭にする',
 		'queries' => 'ユーザークエリ',
 		'queries' => 'ユーザークエリ',
 		'read' => '既読の記事を表示する',
 		'read' => '既読の記事を表示する',
 		'reader_view' => 'リーディングビュー',
 		'reader_view' => 'リーディングビュー',
 		'rss_view' => 'RSSフィード',
 		'rss_view' => 'RSSフィード',
 		'search_short' => '検索',
 		'search_short' => '検索',
 		'sort' => array(
 		'sort' => array(
-			'_' => '並べ替え',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => '公開日順 1→9',
 			'date_asc' => '公開日順 1→9',
 			'date_desc' => '公開日順 9→1',
 			'date_desc' => '公開日順 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'リンクURL順 A→Z',
 			'link_asc' => 'リンクURL順 A→Z',
 			'link_desc' => 'リンクURL順 Z→A',
 			'link_desc' => 'リンクURL順 Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'ランダムに並べる',
 			'rand' => 'ランダムに並べる',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'タイトル順 A→Z',
 			'title_asc' => 'タイトル順 A→Z',
 			'title_desc' => 'タイトル順 Z→A',
 			'title_desc' => 'タイトル順 Z→A',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/ko/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => '라벨에도 적용하기',
 		'show_fav_unread_help' => '라벨에도 적용하기',
 		'sides_close_article' => '글 영역 바깥을 클릭하면 글 접기',
 		'sides_close_article' => '글 영역 바깥을 클릭하면 글 접기',
-		'sort' => array(
-			'_' => '정렬 순서',
-			'newer_first' => '최근 글 먼저',
-			'older_first' => '오래된 글 먼저',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/ko/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => '피드를 읽음으로 표시',
 		'mark_feed_read' => '피드를 읽음으로 표시',
 		'mark_selection_unread' => '선택된 글을 읽지 않음으로 표시',
 		'mark_selection_unread' => '선택된 글을 읽지 않음으로 표시',
 		'mylabels' => '내 라벨',
 		'mylabels' => '내 라벨',
-		'newer_first' => '최근 글 먼저',
 		'non-starred' => '즐겨찾기를 제외하고 표시',
 		'non-starred' => '즐겨찾기를 제외하고 표시',
 		'normal_view' => '일반 모드',
 		'normal_view' => '일반 모드',
-		'older_first' => '오래된 글 먼저',
 		'queries' => '사용자 쿼리',
 		'queries' => '사용자 쿼리',
 		'read' => '읽은 글만 표시',
 		'read' => '읽은 글만 표시',
 		'reader_view' => '읽기 모드',
 		'reader_view' => '읽기 모드',
 		'rss_view' => 'RSS 피드',
 		'rss_view' => 'RSS 피드',
 		'search_short' => '검색',
 		'search_short' => '검색',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/lv/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Attiecas arī uz birkām',
 		'show_fav_unread_help' => 'Attiecas arī uz birkām',
 		'sides_close_article' => 'Spiežot ārpus raksta teksta apgabala, raksts tiek aizvērts',
 		'sides_close_article' => 'Spiežot ārpus raksta teksta apgabala, raksts tiek aizvērts',
-		'sort' => array(
-			'_' => 'Kārtošanas kārtība',
-			'newer_first' => 'Sākumā jaunākos',
-			'older_first' => 'Sākumā vecākos',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/lv/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Atzīmēt barotni kā izlasītu',
 		'mark_feed_read' => 'Atzīmēt barotni kā izlasītu',
 		'mark_selection_unread' => 'Atzīmēt izvēlni kā izlasītu',
 		'mark_selection_unread' => 'Atzīmēt izvēlni kā izlasītu',
 		'mylabels' => 'Manas birkas',
 		'mylabels' => 'Manas birkas',
-		'newer_first' => 'Sākumā jaunākos',
 		'non-starred' => 'Rādīt neiecienītākos',
 		'non-starred' => 'Rādīt neiecienītākos',
 		'normal_view' => 'Parastais skats',
 		'normal_view' => 'Parastais skats',
-		'older_first' => 'Sākumā vecākos',
 		'queries' => 'Lietotāja pieprasījumi',
 		'queries' => 'Lietotāja pieprasījumi',
 		'read' => 'Rādīt izlasītos',
 		'read' => 'Rādīt izlasītos',
 		'reader_view' => 'Lasīšanas skats',
 		'reader_view' => 'Lasīšanas skats',
 		'rss_view' => 'RSS barotne',
 		'rss_view' => 'RSS barotne',
 		'search_short' => 'Meklēt',
 		'search_short' => 'Meklēt',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/nl/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Ook toepassen op labels',
 		'show_fav_unread_help' => 'Ook toepassen op labels',
 		'sides_close_article' => 'Sluit het artikel door buiten de artikeltekst te klikken',
 		'sides_close_article' => 'Sluit het artikel door buiten de artikeltekst te klikken',
-		'sort' => array(
-			'_' => 'Sorteer volgorde',
-			'newer_first' => 'Nieuwste eerst',
-			'older_first' => 'Oudste eerst',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Markeer een artikel als favoriet…',
 			'when' => 'Markeer een artikel als favoriet…',
 		),
 		),

+ 10 - 3
app/i18n/nl/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Markeer feed als gelezen',
 		'mark_feed_read' => 'Markeer feed als gelezen',
 		'mark_selection_unread' => 'Markeer selectie als ongelezen',
 		'mark_selection_unread' => 'Markeer selectie als ongelezen',
 		'mylabels' => 'Mijn labels',
 		'mylabels' => 'Mijn labels',
-		'newer_first' => 'Nieuwste eerst',
 		'non-starred' => 'Niet-favorieten tonen',
 		'non-starred' => 'Niet-favorieten tonen',
 		'normal_view' => 'Normale weergave',
 		'normal_view' => 'Normale weergave',
-		'older_first' => 'Oudste eerst',
 		'queries' => 'Gebruikers queries',
 		'queries' => 'Gebruikers queries',
 		'read' => 'Gelezen tonen',
 		'read' => 'Gelezen tonen',
 		'reader_view' => 'Leesmodus',
 		'reader_view' => 'Leesmodus',
 		'rss_view' => 'RSS-feed',
 		'rss_view' => 'RSS-feed',
 		'search_short' => 'Zoeken',
 		'search_short' => 'Zoeken',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorteercriteria',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Categorie, feedtitels A→Z',
 				'name_asc' => 'Categorie, feedtitels A→Z',
 				'name_desc' => 'Categorie, feedtitels Z→A',
 				'name_desc' => 'Categorie, feedtitels Z→A',
 			),
 			),
 			'date_asc' => 'Publicatiedatum 1→9',
 			'date_asc' => 'Publicatiedatum 1→9',
 			'date_desc' => 'Publicatiedatum 9→1',
 			'date_desc' => 'Publicatiedatum 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feedtitel A→Z',
 				'name_asc' => 'Feedtitel A→Z',
 				'name_desc' => 'Feedtitel Z→A',
 				'name_desc' => 'Feedtitel Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Lengte van inhoud 9→1',
 			'length_desc' => 'Lengte van inhoud 9→1',
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Willekeurige volgorde',
 			'rand' => 'Willekeurige volgorde',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Titel A→Z',
 			'title_asc' => 'Titel A→Z',
 			'title_desc' => 'Titel Z→A',
 			'title_desc' => 'Titel Z→A',
 			'user_modified_asc' => 'Aangepast door gebruiker 1→9',
 			'user_modified_asc' => 'Aangepast door gebruiker 1→9',

+ 0 - 5
app/i18n/oc/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Aplicar tanben a las etiquetas',
 		'show_fav_unread_help' => 'Aplicar tanben a las etiquetas',
 		'sides_close_article' => 'Clicar fòra de la zòna de tèxte tampa l’article',
 		'sides_close_article' => 'Clicar fòra de la zòna de tèxte tampa l’article',
-		'sort' => array(
-			'_' => 'Òrdre de tria',
-			'newer_first' => 'Mai recents en primièr',
-			'older_first' => 'Mai ancians en primièr',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/oc/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Marcar lo flux coma legit',
 		'mark_feed_read' => 'Marcar lo flux coma legit',
 		'mark_selection_unread' => 'Marcar la seleccion coma pas legida',
 		'mark_selection_unread' => 'Marcar la seleccion coma pas legida',
 		'mylabels' => 'Mas etiquetas',
 		'mylabels' => 'Mas etiquetas',
-		'newer_first' => 'Mai recents en primièr',
 		'non-starred' => 'Mostrar los pas favorits',
 		'non-starred' => 'Mostrar los pas favorits',
 		'normal_view' => 'Vista normala',
 		'normal_view' => 'Vista normala',
-		'older_first' => 'Mai ancians en primièr',
 		'queries' => 'Filtres utilizaire',
 		'queries' => 'Filtres utilizaire',
 		'read' => 'Mostrar los legits',
 		'read' => 'Mostrar los legits',
 		'reader_view' => 'Vista lectura',
 		'reader_view' => 'Vista lectura',
 		'rss_view' => 'Flux RSS',
 		'rss_view' => 'Flux RSS',
 		'search_short' => 'Recercar',
 		'search_short' => 'Recercar',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/pl/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Stosuje się również do etykiet',
 		'show_fav_unread_help' => 'Stosuje się również do etykiet',
 		'sides_close_article' => 'Kliknięcie poza zawartością wiadomości zamyka widok wiadomości',
 		'sides_close_article' => 'Kliknięcie poza zawartością wiadomości zamyka widok wiadomości',
-		'sort' => array(
-			'_' => 'Porządek sortowania',
-			'newer_first' => 'Najpierw najnowsze',
-			'older_first' => 'Najpierw najstarsze',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Oznacz artykuł jako ulubiony…',
 			'when' => 'Oznacz artykuł jako ulubiony…',
 		),
 		),

+ 10 - 3
app/i18n/pl/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Oznacz kanał jako przeczytany',
 		'mark_feed_read' => 'Oznacz kanał jako przeczytany',
 		'mark_selection_unread' => 'Oznacz wiadomości jako nieprzeczytane',
 		'mark_selection_unread' => 'Oznacz wiadomości jako nieprzeczytane',
 		'mylabels' => 'Własne etykiety',
 		'mylabels' => 'Własne etykiety',
-		'newer_first' => 'Najpierw najnowsze',
 		'non-starred' => 'Pokaż wiadomości, które nie są ulubione',
 		'non-starred' => 'Pokaż wiadomości, które nie są ulubione',
 		'normal_view' => 'Widok normalny',
 		'normal_view' => 'Widok normalny',
-		'older_first' => 'Najpierw najstarsze',
 		'queries' => 'Zapisane zapytania',
 		'queries' => 'Zapisane zapytania',
 		'read' => 'Pokaż przeczytane',
 		'read' => 'Pokaż przeczytane',
 		'reader_view' => 'Widok czytania',
 		'reader_view' => 'Widok czytania',
 		'rss_view' => 'Kanał RSS',
 		'rss_view' => 'Kanał RSS',
 		'search_short' => 'Szukaj',
 		'search_short' => 'Szukaj',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Kryteria sortowania',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Tytuł kategorii i kanału A→Z',
 				'name_asc' => 'Tytuł kategorii i kanału A→Z',
 				'name_desc' => 'Tytuł kategorii i kanału Z→A',
 				'name_desc' => 'Tytuł kategorii i kanału Z→A',
 			),
 			),
 			'date_asc' => 'Data publikacji 1→9',
 			'date_asc' => 'Data publikacji 1→9',
 			'date_desc' => 'Data publikacji 9→1',
 			'date_desc' => 'Data publikacji 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Tytuł kanału A→Z',
 				'name_asc' => 'Tytuł kanału A→Z',
 				'name_desc' => 'Tytuł kanału Z→A',
 				'name_desc' => 'Tytuł kanału Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Długość zawartości 9→1',
 			'length_desc' => 'Długość zawartości 9→1',
 			'link_asc' => 'Odnośnik A→Z',
 			'link_asc' => 'Odnośnik A→Z',
 			'link_desc' => 'Odnośnik Z→A',
 			'link_desc' => 'Odnośnik Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Losowa kolejność',
 			'rand' => 'Losowa kolejność',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Tytuł A→Z',
 			'title_asc' => 'Tytuł A→Z',
 			'title_desc' => 'Tytuł Z→A',
 			'title_desc' => 'Tytuł Z→A',
 			'user_modified_asc' => 'Zmodyfikowane przez użytkownika 1→9',
 			'user_modified_asc' => 'Zmodyfikowane przez użytkownika 1→9',

+ 0 - 5
app/i18n/pt-BR/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Aplicar também nas tags',
 		'show_fav_unread_help' => 'Aplicar também nas tags',
 		'sides_close_article' => 'Clicando fora da área do texto do artigo fecha o mesmo',
 		'sides_close_article' => 'Clicando fora da área do texto do artigo fecha o mesmo',
-		'sort' => array(
-			'_' => 'Ordem de visualização',
-			'newer_first' => 'Novos primeiro',
-			'older_first' => 'Antigos primeiro',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Marque um artigo como favorito…',
 			'when' => 'Marque um artigo como favorito…',
 		),
 		),

+ 10 - 3
app/i18n/pt-BR/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Marcar feed com lido',
 		'mark_feed_read' => 'Marcar feed com lido',
 		'mark_selection_unread' => 'Marcar seleção como não lida',
 		'mark_selection_unread' => 'Marcar seleção como não lida',
 		'mylabels' => 'Minhas etiquetas',
 		'mylabels' => 'Minhas etiquetas',
-		'newer_first' => 'Novos primeiro',
 		'non-starred' => 'Mostrar itens que não são favoritos',
 		'non-starred' => 'Mostrar itens que não são favoritos',
 		'normal_view' => 'visualização normal',
 		'normal_view' => 'visualização normal',
-		'older_first' => 'Antigos primeiro',
 		'queries' => 'Queries do usuário',
 		'queries' => 'Queries do usuário',
 		'read' => 'Mostrar leitura',
 		'read' => 'Mostrar leitura',
 		'reader_view' => 'Visualização de leitura',
 		'reader_view' => 'Visualização de leitura',
 		'rss_view' => 'Feed RSS',
 		'rss_view' => 'Feed RSS',
 		'search_short' => 'Buscar',
 		'search_short' => 'Buscar',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Critérios de ordenação',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Categoria, títulos dos feeds A→Z',
 				'name_asc' => 'Categoria, títulos dos feeds A→Z',
 				'name_desc' => 'Categoria, títulos dos feeds Z→A',
 				'name_desc' => 'Categoria, títulos dos feeds Z→A',
 			),
 			),
 			'date_asc' => 'Data de publicação 1→9',
 			'date_asc' => 'Data de publicação 1→9',
 			'date_desc' => 'Data de publicação 9→1',
 			'date_desc' => 'Data de publicação 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Título do feed A→Z',
 				'name_asc' => 'Título do feed A→Z',
 				'name_desc' => 'Título do feed Z→A',
 				'name_desc' => 'Título do feed Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Comprimento do conteúdo 9→1',
 			'length_desc' => 'Comprimento do conteúdo 9→1',
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_asc' => 'Link A→Z',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
 			'link_desc' => 'Link Z→A',	// IGNORE
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Ordem aleatória',
 			'rand' => 'Ordem aleatória',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Título A→Z',
 			'title_asc' => 'Título A→Z',
 			'title_desc' => 'Título Z→A',
 			'title_desc' => 'Título Z→A',
 			'user_modified_asc' => 'Modificado pelo usuário 1→9',
 			'user_modified_asc' => 'Modificado pelo usuário 1→9',

+ 0 - 5
app/i18n/pt-PT/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Aplicar também nas tags',
 		'show_fav_unread_help' => 'Aplicar também nas tags',
 		'sides_close_article' => 'Clicando fora da área do texto do artigo fecha o mesmo',
 		'sides_close_article' => 'Clicando fora da área do texto do artigo fecha o mesmo',
-		'sort' => array(
-			'_' => 'Ordem de visualização',
-			'newer_first' => 'Novos primeiro',
-			'older_first' => 'Antigos primeiro',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/pt-PT/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Marcar feed com lido',
 		'mark_feed_read' => 'Marcar feed com lido',
 		'mark_selection_unread' => 'Marcar seleção como não lida',
 		'mark_selection_unread' => 'Marcar seleção como não lida',
 		'mylabels' => 'Minhas etiquetas',
 		'mylabels' => 'Minhas etiquetas',
-		'newer_first' => 'Novos primeiro',
 		'non-starred' => 'Mostrar todos, exceto favoritos',
 		'non-starred' => 'Mostrar todos, exceto favoritos',
 		'normal_view' => 'visualização normal',
 		'normal_view' => 'visualização normal',
-		'older_first' => 'Antigos primeiro',
 		'queries' => 'Queries do utilizador',
 		'queries' => 'Queries do utilizador',
 		'read' => 'Mostrar apenas lidos',
 		'read' => 'Mostrar apenas lidos',
 		'reader_view' => 'Visualização de leitura',
 		'reader_view' => 'Visualização de leitura',
 		'rss_view' => 'Feed RSS',
 		'rss_view' => 'Feed RSS',
 		'search_short' => 'Pesquisar',
 		'search_short' => 'Pesquisar',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/ru/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Также относится к меткам',
 		'show_fav_unread_help' => 'Также относится к меткам',
 		'sides_close_article' => 'Нажатия мышью за пределами текста статьи закрывают статью',
 		'sides_close_article' => 'Нажатия мышью за пределами текста статьи закрывают статью',
-		'sort' => array(
-			'_' => 'Порядок сортировки',
-			'newer_first' => 'Сначала новые',
-			'older_first' => 'Сначала старые',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Отмечать статью избранной…',
 			'when' => 'Отмечать статью избранной…',
 		),
 		),

+ 10 - 3
app/i18n/ru/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Отметить ленту прочитанной',
 		'mark_feed_read' => 'Отметить ленту прочитанной',
 		'mark_selection_unread' => 'Отметить выделение прочитанным',
 		'mark_selection_unread' => 'Отметить выделение прочитанным',
 		'mylabels' => 'Мои метки',
 		'mylabels' => 'Мои метки',
-		'newer_first' => 'Сначала новые',
 		'non-starred' => 'Показать неизбранное',
 		'non-starred' => 'Показать неизбранное',
 		'normal_view' => 'Обычный вид',
 		'normal_view' => 'Обычный вид',
-		'older_first' => 'Сначала старые',
 		'queries' => 'Запросы',
 		'queries' => 'Запросы',
 		'read' => 'Показать прочитанное',
 		'read' => 'Показать прочитанное',
 		'reader_view' => 'Вид для чтения',
 		'reader_view' => 'Вид для чтения',
 		'rss_view' => 'RSS-лента',
 		'rss_view' => 'RSS-лента',
 		'search_short' => 'Поиск',
 		'search_short' => 'Поиск',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Критерии сортировки',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Категории, названия лент А→Я',
 				'name_asc' => 'Категории, названия лент А→Я',
 				'name_desc' => 'Категории, названия лент Я→А',
 				'name_desc' => 'Категории, названия лент Я→А',
 			),
 			),
 			'date_asc' => 'Дата публикации 1→9',
 			'date_asc' => 'Дата публикации 1→9',
 			'date_desc' => 'Дата публикации 9→1',
 			'date_desc' => 'Дата публикации 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Названия лент А→Я',
 				'name_asc' => 'Названия лент А→Я',
 				'name_desc' => 'Названия лент Я→А',
 				'name_desc' => 'Названия лент Я→А',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Длина контента 9→1',
 			'length_desc' => 'Длина контента 9→1',
 			'link_asc' => 'Ссылка А→Я',
 			'link_asc' => 'Ссылка А→Я',
 			'link_desc' => 'Ссылка Я→А',
 			'link_desc' => 'Ссылка Я→А',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Случайный порядок',
 			'rand' => 'Случайный порядок',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Заголовок А→Я',
 			'title_asc' => 'Заголовок А→Я',
 			'title_desc' => 'Заголовок Я→А',
 			'title_desc' => 'Заголовок Я→А',
 			'user_modified_asc' => 'Изменено пользователем 1→9',
 			'user_modified_asc' => 'Изменено пользователем 1→9',

+ 0 - 5
app/i18n/sk/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Týka sa aj štítkov',
 		'show_fav_unread_help' => 'Týka sa aj štítkov',
 		'sides_close_article' => 'Po kliknutí mimo textu článku sa článok zatvorí',
 		'sides_close_article' => 'Po kliknutí mimo textu článku sa článok zatvorí',
-		'sort' => array(
-			'_' => 'Poradie',
-			'newer_first' => 'Novšie hore',
-			'older_first' => 'Staršie hore',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Mark an article as favourite…',	// TODO
 			'when' => 'Mark an article as favourite…',	// TODO
 		),
 		),

+ 10 - 3
app/i18n/sk/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Označiť kanál ako prečítaný',
 		'mark_feed_read' => 'Označiť kanál ako prečítaný',
 		'mark_selection_unread' => 'Označiť označené ako prečítané',
 		'mark_selection_unread' => 'Označiť označené ako prečítané',
 		'mylabels' => 'Moje nálepky',
 		'mylabels' => 'Moje nálepky',
-		'newer_first' => 'Novšie hore',
 		'non-starred' => 'Zobraziť všetko okrem obľúbených',
 		'non-starred' => 'Zobraziť všetko okrem obľúbených',
 		'normal_view' => 'Základné zobrazenie',
 		'normal_view' => 'Základné zobrazenie',
-		'older_first' => 'Staršie hore',
 		'queries' => 'Používateľské dopyty',
 		'queries' => 'Používateľské dopyty',
 		'read' => 'Zobraziť prečítané',
 		'read' => 'Zobraziť prečítané',
 		'reader_view' => 'Zobrazenie na čítanie',
 		'reader_view' => 'Zobrazenie na čítanie',
 		'rss_view' => 'RSS kanál',
 		'rss_view' => 'RSS kanál',
 		'search_short' => 'Hľadať',
 		'search_short' => 'Hľadať',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/tr/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Etiketler için de geçerlidir',
 		'show_fav_unread_help' => 'Etiketler için de geçerlidir',
 		'sides_close_article' => 'Makale metin alanının dışına tıklayınca makaleyi kapat',
 		'sides_close_article' => 'Makale metin alanının dışına tıklayınca makaleyi kapat',
-		'sort' => array(
-			'_' => 'Sıralama düzeni',
-			'newer_first' => 'Önce yeniler',
-			'older_first' => 'Önce eskiler',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Bir makaleyi favori olarak işaretle…',
 			'when' => 'Bir makaleyi favori olarak işaretle…',
 		),
 		),

+ 10 - 3
app/i18n/tr/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Beslemeyi okundu olarak işaretle',
 		'mark_feed_read' => 'Beslemeyi okundu olarak işaretle',
 		'mark_selection_unread' => 'Seçimi okunmadı olarak işaretle',
 		'mark_selection_unread' => 'Seçimi okunmadı olarak işaretle',
 		'mylabels' => 'Etiketlerim',
 		'mylabels' => 'Etiketlerim',
-		'newer_first' => 'Önce yeniler',
 		'non-starred' => 'Favori olmayanları göster',
 		'non-starred' => 'Favori olmayanları göster',
 		'normal_view' => 'Normal görünüm',
 		'normal_view' => 'Normal görünüm',
-		'older_first' => 'Önce eskiler',
 		'queries' => 'Kullanıcı sorguları',
 		'queries' => 'Kullanıcı sorguları',
 		'read' => 'Okunanları göster',
 		'read' => 'Okunanları göster',
 		'reader_view' => 'Okuma görünümü',
 		'reader_view' => 'Okuma görünümü',
 		'rss_view' => 'RSS beslemesi',
 		'rss_view' => 'RSS beslemesi',
 		'search_short' => 'Ara',
 		'search_short' => 'Ara',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sıralama kriteri',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Yayın tarihi 1→9',
 			'date_asc' => 'Yayın tarihi 1→9',
 			'date_desc' => 'Yayın tarihi 9→1',
 			'date_desc' => 'Yayın tarihi 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Bağlantı A→Z',
 			'link_asc' => 'Bağlantı A→Z',
 			'link_desc' => 'Bağlantı Z→A',
 			'link_desc' => 'Bağlantı Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Rastgele sıralama',
 			'rand' => 'Rastgele sıralama',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Başlık A→Z',
 			'title_asc' => 'Başlık A→Z',
 			'title_desc' => 'Başlık Z→A',
 			'title_desc' => 'Başlık Z→A',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/uk/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => 'Впливає також на мітки',
 		'show_fav_unread_help' => 'Впливає також на мітки',
 		'sides_close_article' => 'Натиск за межами тексту статті закриває статтю',
 		'sides_close_article' => 'Натиск за межами тексту статті закриває статтю',
-		'sort' => array(
-			'_' => 'Порядок',
-			'newer_first' => 'Спершу новіші',
-			'older_first' => 'Спершу старіші',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => 'Вподобати статтю…',
 			'when' => 'Вподобати статтю…',
 		),
 		),

+ 10 - 3
app/i18n/uk/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => 'Позначити стрічку прочитаною',
 		'mark_feed_read' => 'Позначити стрічку прочитаною',
 		'mark_selection_unread' => 'Позначити вибрані непрочитаними',
 		'mark_selection_unread' => 'Позначити вибрані непрочитаними',
 		'mylabels' => 'Мої мітки',
 		'mylabels' => 'Мої мітки',
-		'newer_first' => 'Спершу новіші',
 		'non-starred' => 'Показати невподобані',
 		'non-starred' => 'Показати невподобані',
 		'normal_view' => 'Звичайний показ',
 		'normal_view' => 'Звичайний показ',
-		'older_first' => 'Спершу старіші',
 		'queries' => 'Користувацькі запити',
 		'queries' => 'Користувацькі запити',
 		'read' => 'Показати прочитані',
 		'read' => 'Показати прочитані',
 		'reader_view' => 'Читацький показ',
 		'reader_view' => 'Читацький показ',
 		'rss_view' => 'RSS-стрічка',
 		'rss_view' => 'RSS-стрічка',
 		'search_short' => 'Пошук',
 		'search_short' => 'Пошук',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Критерії впорядкування',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Заголовки категорії та стрічки А→Я',
 				'name_asc' => 'Заголовки категорії та стрічки А→Я',
 				'name_desc' => 'Заголовки категорії та стрічки Я→А',
 				'name_desc' => 'Заголовки категорії та стрічки Я→А',
 			),
 			),
 			'date_asc' => 'Дата оприлюднення 1→9',
 			'date_asc' => 'Дата оприлюднення 1→9',
 			'date_desc' => 'Дата оприлюднення 9→1',
 			'date_desc' => 'Дата оприлюднення 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Назва стрічки A→Z',
 				'name_asc' => 'Назва стрічки A→Z',
 				'name_desc' => 'Назва стрічки Z→A',
 				'name_desc' => 'Назва стрічки Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Посилання А→Я',
 			'link_asc' => 'Посилання А→Я',
 			'link_desc' => 'Посилання Я→А',
 			'link_desc' => 'Посилання Я→А',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Довільний порядок',
 			'rand' => 'Довільний порядок',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Заголовок А→Я',
 			'title_asc' => 'Заголовок А→Я',
 			'title_desc' => 'Заголовок Я→А',
 			'title_desc' => 'Заголовок Я→А',
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 0 - 5
app/i18n/zh-CN/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => '同样适用于标签',
 		'show_fav_unread_help' => '同样适用于标签',
 		'sides_close_article' => '点击文章文本区域外关闭文章',
 		'sides_close_article' => '点击文章文本区域外关闭文章',
-		'sort' => array(
-			'_' => '排列顺序',
-			'newer_first' => '由新至旧',
-			'older_first' => '由旧至新',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => '将文章标记为收藏时…',
 			'when' => '将文章标记为收藏时…',
 		),
 		),

+ 10 - 3
app/i18n/zh-CN/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => '此订阅源设为已读',
 		'mark_feed_read' => '此订阅源设为已读',
 		'mark_selection_unread' => '将筛选结果标记为未读',
 		'mark_selection_unread' => '将筛选结果标记为未读',
 		'mylabels' => '我的标签',
 		'mylabels' => '我的标签',
-		'newer_first' => '由新至旧',
 		'non-starred' => '显示未收藏',
 		'non-starred' => '显示未收藏',
 		'normal_view' => '普通视图',
 		'normal_view' => '普通视图',
-		'older_first' => '由旧至新',
 		'queries' => '自定义查询',
 		'queries' => '自定义查询',
 		'read' => '显示已读',
 		'read' => '显示已读',
 		'reader_view' => '阅读视图',
 		'reader_view' => '阅读视图',
 		'rss_view' => '订阅源',
 		'rss_view' => '订阅源',
 		'search_short' => '搜索',
 		'search_short' => '搜索',
 		'sort' => array(
 		'sort' => array(
-			'_' => '排序标准',
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => '分类、订阅源标题 A→Z',
 				'name_asc' => '分类、订阅源标题 A→Z',
 				'name_desc' => '分类、订阅源标题 Z→A',
 				'name_desc' => '分类、订阅源标题 Z→A',
 			),
 			),
 			'date_asc' => '发布日期 1→9',
 			'date_asc' => '发布日期 1→9',
 			'date_desc' => '发布日期 9→1',
 			'date_desc' => '发布日期 9→1',
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => '订阅源标题 A→Z',
 				'name_asc' => '订阅源标题 A→Z',
 				'name_desc' => '订阅源标题 Z→A',
 				'name_desc' => '订阅源标题 Z→A',
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => '内容长度 9→1',
 			'length_desc' => '内容长度 9→1',
 			'link_asc' => '链接 A→Z',
 			'link_asc' => '链接 A→Z',
 			'link_desc' => '链接 Z→A',
 			'link_desc' => '链接 Z→A',
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => '随机顺序',
 			'rand' => '随机顺序',
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => '标题 A→Z',
 			'title_asc' => '标题 A→Z',
 			'title_desc' => '标题 Z→A',
 			'title_desc' => '标题 Z→A',
 			'user_modified_asc' => '用户修改 1→9',
 			'user_modified_asc' => '用户修改 1→9',

+ 0 - 5
app/i18n/zh-TW/conf.php

@@ -294,11 +294,6 @@ return array(
 		),
 		),
 		'show_fav_unread_help' => '同樣適用於標籤',
 		'show_fav_unread_help' => '同樣適用於標籤',
 		'sides_close_article' => '點擊文章區域外以關閉',
 		'sides_close_article' => '點擊文章區域外以關閉',
-		'sort' => array(
-			'_' => '排列順序',
-			'newer_first' => '由新至舊',
-			'older_first' => '由舊至新',
-		),
 		'star' => array(
 		'star' => array(
 			'when' => '標記一篇文章為最愛…',
 			'when' => '標記一篇文章為最愛…',
 		),
 		),

+ 10 - 3
app/i18n/zh-TW/index.php

@@ -77,23 +77,22 @@ return array(
 		'mark_feed_read' => '此訂閱源設為已讀',
 		'mark_feed_read' => '此訂閱源設為已讀',
 		'mark_selection_unread' => '選中設為已讀',
 		'mark_selection_unread' => '選中設為已讀',
 		'mylabels' => '我的標籤',
 		'mylabels' => '我的標籤',
-		'newer_first' => '由新至舊',
 		'non-starred' => '顯示未收藏',
 		'non-starred' => '顯示未收藏',
 		'normal_view' => '普通視圖',
 		'normal_view' => '普通視圖',
-		'older_first' => '由舊至新',
 		'queries' => '自定義查詢',
 		'queries' => '自定義查詢',
 		'read' => '顯示已讀',
 		'read' => '顯示已讀',
 		'reader_view' => '閱讀視圖',
 		'reader_view' => '閱讀視圖',
 		'rss_view' => '訂閱源',
 		'rss_view' => '訂閱源',
 		'search_short' => '搜尋',
 		'search_short' => '搜尋',
 		'sort' => array(
 		'sort' => array(
-			'_' => 'Sorting criteria',	// TODO
+			'asc' => 'Ascending',	// TODO
 			'c' => array(
 			'c' => array(
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_asc' => 'Category, feed titles A→Z',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 				'name_desc' => 'Category, feed titles Z→A',	// TODO
 			),
 			),
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_asc' => 'Publication date 1→9',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
 			'date_desc' => 'Publication date 9→1',	// TODO
+			'desc' => 'Descending',	// TODO
 			'f' => array(
 			'f' => array(
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_asc' => 'Feed title A→Z',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
 				'name_desc' => 'Feed title Z→A',	// TODO
@@ -104,7 +103,15 @@ return array(
 			'length_desc' => 'Content length 9→1',	// TODO
 			'length_desc' => 'Content length 9→1',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_asc' => 'Link A→Z',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
 			'link_desc' => 'Link Z→A',	// TODO
+			'primary' => array(
+				'_' => 'Sorting criterion',	// TODO
+				'help' => 'Sorting by <em>received</em> date is recommended in most cases, for consistency and performance',	// TODO
+			),
 			'rand' => 'Random order',	// TODO
 			'rand' => 'Random order',	// TODO
+			'secondary' => array(
+				'_' => 'Secondary sorting criterion',	// TODO
+				'help' => 'Only relevant when the primary sorting criterion is categories or feeds titles',	// TODO
+			),
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_asc' => 'Title A→Z',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'title_desc' => 'Title Z→A',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO
 			'user_modified_asc' => 'User modified 1→9',	// TODO

+ 3 - 3
app/layout/nav_menu.phtml

@@ -227,17 +227,17 @@
 	<?php
 	<?php
 		if (FreshRSS_Context::$order === 'ASC') {
 		if (FreshRSS_Context::$order === 'ASC') {
 			$icon = 'sort-up';
 			$icon = 'sort-up';
-			$title = _t('index.menu.older_first');
+			$title = _t('index.menu.sort.asc');
 		} else {
 		} else {
 			$icon = 'sort-down';
 			$icon = 'sort-down';
-			$title = _t('index.menu.newer_first');
+			$title = _t('index.menu.sort.desc');
 		}
 		}
 		$url_order = Minz_Request::currentRequest();
 		$url_order = Minz_Request::currentRequest();
 	?>
 	?>
 	<div id="nav_menu_sort" class="group">
 	<div id="nav_menu_sort" class="group">
 		<div class="dropdown">
 		<div class="dropdown">
 			<div id="dropdown-sort" class="dropdown-target"></div>
 			<div id="dropdown-sort" class="dropdown-target"></div>
-			<a id="toggle-order" class="dropdown-toggle btn" href="#dropdown-sort" title="<?= _t('index.menu.sort') ?>"><?= _i($icon) ?></a>
+			<a id="toggle-order" class="dropdown-toggle btn" href="#dropdown-sort" title="<?= _t('index.menu.sort.primary') ?>"><?= _i($icon) ?></a>
 			<ul class="dropdown-menu" role="radiogroup">
 			<ul class="dropdown-menu" role="radiogroup">
 				<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'id' ? 'true' : 'false' ?>">
 				<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'id' ? 'true' : 'false' ?>">
 					<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'id', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.id_desc') ?></a></li>
 					<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'id', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.id_desc') ?></a></li>

+ 48 - 5
app/views/configure/reading.phtml

@@ -74,16 +74,59 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			<?php
+				$userSort = FreshRSS_Context::userConf()->sort;
+				$userSortOrder = FreshRSS_Context::userConf()->sort_order;
+				$userSortCombined = $userSort === 'rand' ? 'rand' : $userSort . '_' . strtolower($userSortOrder ?? 'desc');
+			?>
 			<div class="form-group">
 			<div class="form-group">
-				<label class="group-name" for="sort_order"><?= _t('conf.reading.sort') ?></label>
-				<div class="group-controls">
-					<select name="sort_order" id="sort_order">
-						<option value="DESC"<?= FreshRSS_Context::userConf()->sort_order === 'DESC' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.sort.newer_first') ?></option>
-						<option value="ASC"<?= FreshRSS_Context::userConf()->sort_order === 'ASC' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.sort.older_first') ?></option>
+				<label class="group-name" for="primary_sort"><?= _t('index.menu.sort.primary') ?></label>
+				<div class="group-controls">
+					<select name="primary_sort" id="primary_sort">
+						<option value="id_desc" <?= $userSortCombined === 'id_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_desc') ?></option>
+						<option value="date_desc" <?= $userSortCombined === 'date_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_desc') ?></option>
+						<option value="length_desc" <?= $userSortCombined === 'length_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_desc') ?></option>
+						<option value="link_desc" <?= $userSortCombined === 'link_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_desc') ?></option>
+						<option value="title_desc" <?= $userSortCombined === 'title_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_desc') ?></option>
+						<option value="f.name_desc" <?= $userSortCombined === 'f.name_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.f.name_desc') ?></option>
+						<option value="c.name_desc" <?= $userSortCombined === 'c.name_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.c.name_desc') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="rand" <?= $userSortCombined === 'rand' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.rand') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="id_asc" <?= $userSortCombined === 'id_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_asc') ?></option>
+						<option value="date_asc" <?= $userSortCombined === 'date_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_asc') ?></option>
+						<option value="length_asc" <?= $userSortCombined === 'length_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_asc') ?></option>
+						<option value="link_asc" <?= $userSortCombined === 'link_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_asc') ?></option>
+						<option value="title_asc" <?= $userSortCombined === 'title_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_asc') ?></option>
+						<option value="f.name_asc" <?= $userSortCombined === 'f.name_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.f.name_asc') ?></option>
+						<option value="c.name_asc" <?= $userSortCombined === 'c.name_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.c.name_asc') ?></option>
 					</select>
 					</select>
+					<p class="help"><?= _i('help') ?> <?= _t('index.menu.sort.primary.help') ?></p>
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			<?php
+				$userSecondarySort = FreshRSS_Context::userConf()->secondary_sort;
+				$userSecondarySortOrder = FreshRSS_Context::userConf()->secondary_sort_order;
+				$userSecondarySortCombined = $userSecondarySort === 'rand' ? 'rand' : $userSecondarySort . '_' . strtolower($userSecondarySortOrder ?? 'desc');
+			?>
+			<div class="form-group">
+				<label class="group-name" for="secondary_sort"><?= _t('index.menu.sort.secondary') ?></label>
+				<div class="group-controls">
+					<select name="secondary_sort" id="secondary_sort">
+						<option value="id_desc" <?= $userSecondarySortCombined === 'id_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_desc') ?></option>
+						<option value="date_desc" <?= $userSecondarySortCombined === 'date_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_desc') ?></option>
+						<option value="link_desc" <?= $userSecondarySortCombined === 'link_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_desc') ?></option>
+						<option value="title_desc" <?= $userSecondarySortCombined === 'title_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_desc') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="id_asc" <?= $userSecondarySortCombined === 'id_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_asc') ?></option>
+						<option value="date_asc" <?= $userSecondarySortCombined === 'date_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_asc') ?></option>
+						<option value="link_asc" <?= $userSecondarySortCombined === 'link_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_asc') ?></option>
+						<option value="title_asc" <?= $userSecondarySortCombined === 'title_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_asc') ?></option>
+					</select>
+					<p class="help"><?= _i('help') ?> <?= _t('index.menu.sort.secondary.help') ?></p>
+				</div>
+			</div>
 		</fieldset>
 		</fieldset>
 
 
 		<fieldset>
 		<fieldset>

+ 31 - 0
app/views/helpers/category/update.phtml

@@ -36,6 +36,37 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			<?php
+				$categoryDefaultSort = $this->category->defaultSort();
+				$categoryDefaultOrder = $this->category->defaultOrder();
+				$categoryDefaultSortOrder = $categoryDefaultSort !== null
+					? ($categoryDefaultSort === 'rand' ? 'rand' : $categoryDefaultSort . '_' . strtolower($categoryDefaultOrder ?? 'desc'))
+					: '';
+			?>
+			<div class="form-group">
+				<label class="group-name" for="defaultSortOrder"><?= _t('index.menu.sort.primary') ?></label>
+				<div class="group-controls">
+					<select name="defaultSortOrder" id="defaultSortOrderCategory" class="w50">
+						<option value=""></option>
+						<option value="id_desc" <?= $categoryDefaultSortOrder === 'id_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_desc') ?></option>
+						<option value="date_desc" <?= $categoryDefaultSortOrder === 'date_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_desc') ?></option>
+						<option value="length_desc" <?= $categoryDefaultSortOrder === 'length_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_desc') ?></option>
+						<option value="link_desc" <?= $categoryDefaultSortOrder === 'link_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_desc') ?></option>
+						<option value="title_desc" <?= $categoryDefaultSortOrder === 'title_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_desc') ?></option>
+						<option value="f.name_desc" <?= $categoryDefaultSortOrder === 'f.name_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.f.name_desc') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="rand" <?= $categoryDefaultSortOrder === 'rand' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.rand') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="id_asc" <?= $categoryDefaultSortOrder === 'id_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_asc') ?></option>
+						<option value="date_asc" <?= $categoryDefaultSortOrder === 'date_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_asc') ?></option>
+						<option value="length_asc" <?= $categoryDefaultSortOrder === 'length_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_asc') ?></option>
+						<option value="link_asc" <?= $categoryDefaultSortOrder === 'link_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_asc') ?></option>
+						<option value="title_asc" <?= $categoryDefaultSortOrder === 'title_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_asc') ?></option>
+						<option value="f.name_asc" <?= $categoryDefaultSortOrder === 'f.name_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.f.name_asc') ?></option>
+					</select>
+				</div>
+			</div>
+
 			<div class="form-group form-actions">
 			<div class="form-group form-actions">
 				<div class="group-controls">
 				<div class="group-controls">
 					<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
 					<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>

+ 30 - 0
app/views/helpers/feed/update.phtml

@@ -130,6 +130,36 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+
+			<?php
+				$feedDefaultSort = $this->feed->defaultSort();
+				$feedDefaultOrder = $this->feed->defaultOrder();
+				$feedDefaultSortOrder = $feedDefaultSort !== null
+					? ($feedDefaultSort === 'rand' ? 'rand' : $feedDefaultSort . '_' . strtolower($feedDefaultOrder ?? 'desc'))
+					: '';
+			?>
+			<div class="form-group">
+				<label class="group-name" for="defaultSortOrder"><?= _t('index.menu.sort.primary') ?></label>
+				<div class="group-controls">
+					<select name="defaultSortOrder" id="defaultSortOrderFeed" class="w50">
+						<option value=""></option>
+						<option value="id_desc" <?= $feedDefaultSortOrder === 'id_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_desc') ?></option>
+						<option value="date_desc" <?= $feedDefaultSortOrder === 'date_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_desc') ?></option>
+						<option value="length_desc" <?= $feedDefaultSortOrder === 'length_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_desc') ?></option>
+						<option value="link_desc" <?= $feedDefaultSortOrder === 'link_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_desc') ?></option>
+						<option value="title_desc" <?= $feedDefaultSortOrder === 'title_desc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_desc') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="rand" <?= $feedDefaultSortOrder === 'rand' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.rand') ?></option>
+						<option disabled="disabled">────────────────</option>
+						<option value="id_asc" <?= $feedDefaultSortOrder === 'id_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.id_asc') ?></option>
+						<option value="date_asc" <?= $feedDefaultSortOrder === 'date_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.date_asc') ?></option>
+						<option value="length_asc" <?= $feedDefaultSortOrder === 'length_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.length_asc') ?></option>
+						<option value="link_asc" <?= $feedDefaultSortOrder === 'link_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.link_asc') ?></option>
+						<option value="title_asc" <?= $feedDefaultSortOrder === 'title_asc' ? 'selected="selected"' : '' ?>><?= _t('index.menu.sort.title_asc') ?></option>
+					</select>
+				</div>
+			</div>
+
 			<div class="form-group">
 			<div class="form-group">
 				<label class="group-name" for="unicityCriteria"><?= _t('sub.feed.unicityCriteria') ?></label>
 				<label class="group-name" for="unicityCriteria"><?= _t('sub.feed.unicityCriteria') ?></label>
 				<?php
 				<?php

+ 6 - 2
config-user.default.php

@@ -53,10 +53,13 @@ return array (
 	#	Set to `true` to mark it unread, or `false` to leave it as-is.
 	#	Set to `true` to mark it unread, or `false` to leave it as-is.
 	'mark_updated_article_unread' => false, //TODO: -1 => ignore, 0 => update, 1 => update and mark as unread
 	'mark_updated_article_unread' => false, //TODO: -1 => ignore, 0 => update, 1 => update and mark as unread
 
 
-	# 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified'|'length'
+	# 'id'|'c.name'|'date'|'f.name'|'length'|'link'|'rand'|'title'
 	'sort' => 'id',
 	'sort' => 'id',
-	'mark_read_button' => 'big',
 	'sort_order' => 'DESC',
 	'sort_order' => 'DESC',
+	# 'id'|'date'|'link'|'title'
+	'secondary_sort' => 'id',
+	'secondary_sort_order' => 'DESC',
+
 	'anon_access' => false,
 	'anon_access' => false,
 	'mark_when' => array (
 	'mark_when' => array (
 		'article' => true,
 		'article' => true,
@@ -105,6 +108,7 @@ return array (
 	# Hide the dropdown configuration menu and favicon in the aside list in case of many feeds, for UI performance
 	# Hide the dropdown configuration menu and favicon in the aside list in case of many feeds, for UI performance
 	'simplify_over_n_feeds' => 1000,
 	'simplify_over_n_feeds' => 1000,
 
 
+	'mark_read_button' => 'big',
 	'topline_read' => true,
 	'topline_read' => true,
 	'topline_favorite' => true,
 	'topline_favorite' => true,
 	'topline_myLabels' => false,
 	'topline_myLabels' => false,