> */
+ public function enclosures(bool $searchBodyImages = false) {
+ $attributeEnclosures = $this->attributes('enclosures');
+ if (is_array($attributeEnclosures)) {
+ // FreshRSS 1.20.1+: The enclosures are saved as attributes
+ yield from $attributeEnclosures;
+ }
try {
- $searchEnclosures = strpos($this->content, 'content, '
query('//div[@class="enclosure"]/p[@class="enclosure-content"]/*[@src]');
foreach ($enclosures as $enclosure) {
$result = [
@@ -148,7 +257,7 @@ class FreshRSS_Entry extends Minz_Model {
case 'audio': $result['medium'] = 'audio'; break;
}
}
- $results[] = $result;
+ yield Minz_Helper::htmlspecialchars_utf8($result);
}
}
if ($searchBodyImages) {
@@ -159,26 +268,31 @@ class FreshRSS_Entry extends Minz_Model {
$src = $img->getAttribute('data-src');
}
if ($src != null) {
- $results[] = [
+ $result = [
'url' => $src,
- 'alt' => $img->getAttribute('alt'),
];
+ yield Minz_Helper::htmlspecialchars_utf8($result);
}
}
}
- return $results;
} catch (Exception $ex) {
- return $results;
+ Minz_Log::debug(__METHOD__ . ' ' . $ex->getMessage());
}
}
/**
* @return array|null
*/
- public function thumbnail() {
- foreach ($this->enclosures(true) as $enclosure) {
- if (!empty($enclosure['url']) && empty($enclosure['type'])) {
- return $enclosure;
+ public function thumbnail(bool $searchEnclosures = true) {
+ $thumbnail = $this->attributes('thumbnail');
+ if (!empty($thumbnail['url'])) {
+ return $thumbnail;
+ }
+ if ($searchEnclosures) {
+ foreach ($this->enclosures(true) as $enclosure) {
+ if (self::enclosureIsImage($enclosure)) {
+ return $enclosure;
+ }
}
}
return null;
@@ -188,6 +302,7 @@ class FreshRSS_Entry extends Minz_Model {
public function link(): string {
return $this->link;
}
+ /** @return string|int */
public function date(bool $raw = false) {
if ($raw) {
return $this->date;
@@ -587,7 +702,7 @@ class FreshRSS_Entry extends Minz_Model {
if ($entry) {
// l’article existe déjà en BDD, en se contente de recharger ce contenu
- $this->content = $entry->content();
+ $this->content = $entry->content(false);
} else {
try {
// The article is not yet in the database, so let’s fetch it
@@ -629,7 +744,7 @@ class FreshRSS_Entry extends Minz_Model {
'guid' => $this->guid(),
'title' => $this->title(),
'author' => $this->authors(true),
- 'content' => $this->content(),
+ 'content' => $this->content(false),
'link' => $this->link(),
'date' => $this->date(true),
'hash' => $this->hash(),
@@ -677,7 +792,6 @@ class FreshRSS_Entry extends Minz_Model {
'published' => $this->date(true),
// 'updated' => $this->date(true),
'title' => $this->title(),
- 'summary' => ['content' => $this->content()],
'canonical' => [
['href' => htmlspecialchars_decode($this->link(), ENT_QUOTES)],
],
@@ -697,13 +811,16 @@ class FreshRSS_Entry extends Minz_Model {
if ($mode === 'compat') {
$item['title'] = escapeToUnicodeAlternative($this->title(), false);
unset($item['alternate'][0]['type']);
- if (mb_strlen($this->content(), 'UTF-8') > self::API_MAX_COMPAT_CONTENT_LENGTH) {
- $item['summary']['content'] = mb_strcut($this->content(), 0, self::API_MAX_COMPAT_CONTENT_LENGTH, 'UTF-8');
- }
- } elseif ($mode === 'freshrss') {
+ $item['summary'] = [
+ 'content' => mb_strcut($this->content(true), 0, self::API_MAX_COMPAT_CONTENT_LENGTH, 'UTF-8'),
+ ];
+ } else {
+ $item['content'] = [
+ 'content' => $this->content(false),
+ ];
+ }
+ if ($mode === 'freshrss') {
$item['guid'] = $this->guid();
- unset($item['summary']);
- $item['content'] = ['content' => $this->content()];
}
if ($category != null && $mode !== 'freshrss') {
$item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($category->name(), ENT_QUOTES);
@@ -718,10 +835,11 @@ class FreshRSS_Entry extends Minz_Model {
}
}
foreach ($this->enclosures() as $enclosure) {
- if (!empty($enclosure['url']) && !empty($enclosure['type'])) {
+ if (!empty($enclosure['url'])) {
$media = [
'href' => $enclosure['url'],
- 'type' => $enclosure['type'],
+ 'type' => $enclosure['type'] ?? $enclosure['medium'] ??
+ (self::enclosureIsImage($enclosure) ? 'image' : ''),
];
if (!empty($enclosure['length'])) {
$media['length'] = intval($enclosure['length']);
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index b63515223..3b7c1ac3f 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -10,6 +10,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return true;
}
+ protected static function sqlConcat($s1, $s2) {
+ return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL
+ }
+
public static function sqlHexDecode(string $x): string {
return 'unhex(' . $x . ')';
}
@@ -943,8 +947,8 @@ SQL;
}
if ($filter->getTags()) {
foreach ($filter->getTags() as $tag) {
- $sub_search .= 'AND ' . $alias . 'tags LIKE ? ';
- $values[] = "%{$tag}%";
+ $sub_search .= 'AND ' . static::sqlConcat('TRIM(' . $alias . 'tags) ', " ' #'") . ' LIKE ? ';
+ $values[] = "%{$tag} #%";
}
}
if ($filter->getInurl()) {
@@ -968,8 +972,8 @@ SQL;
}
if ($filter->getNotTags()) {
foreach ($filter->getNotTags() as $tag) {
- $sub_search .= 'AND ' . $alias . 'tags NOT LIKE ? ';
- $values[] = "%{$tag}%";
+ $sub_search .= 'AND ' . static::sqlConcat('TRIM(' . $alias . 'tags) ', " ' #'") . ' NOT LIKE ? ';
+ $values[] = "%{$tag} #%";
}
}
if ($filter->getNotInurl()) {
@@ -1161,10 +1165,12 @@ SQL;
}
}
- public function listByIds($ids, $order = 'DESC') {
+ /** @param array $ids */
+ public function listByIds(array $ids, string $order = 'DESC') {
if (count($ids) < 1) {
- yield false;
- } elseif (count($ids) > FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER) {
+ return;
+ }
+ if (count($ids) > FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER) {
// Split a query with too many variables parameters
$idsChunks = array_chunk($ids, FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER);
foreach ($idsChunks as $idsChunk) {
@@ -1191,15 +1197,16 @@ SQL;
/**
* For API
+ * @return array
*/
public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL,
- $order = 'DESC', $limit = 1, $firstId = '', $filters = null) {
+ $order = 'DESC', $limit = 1, $firstId = '', $filters = null): array {
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters);
$stm = $this->pdo->prepare($sql);
$stm->execute($values);
- return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ return $stm->fetchAll(PDO::FETCH_COLUMN, 0) ?: [];
}
public function listHashForFeedGuids($id_feed, $guids) {
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 8039581e6..35f3ef676 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -10,6 +10,10 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
return false;
}
+ protected static function sqlConcat($s1, $s2) {
+ return $s1 . '||' . $s2;
+ }
+
public static function sqlHexDecode(string $x): string {
return $x;
}
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index f24ec1884..7c46199a5 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -17,6 +17,11 @@ class FreshRSS_Feed extends Minz_Model {
* @var int
*/
const KIND_HTML_XPATH = 10;
+ /**
+ * Normal XML with XPath scraping
+ * @var int
+ */
+ const KIND_XML_XPATH = 15;
/**
* Normal JSON with XPath scraping
* @var int
@@ -259,13 +264,14 @@ class FreshRSS_Feed extends Minz_Model {
}
public function _url(string $value, bool $validate = true) {
$this->hash = '';
+ $url = $value;
if ($validate) {
- $value = checkUrl($value);
+ $url = checkUrl($url);
}
- if ($value == '') {
+ if ($url == '') {
throw new FreshRSS_BadUrl_Exception($value);
}
- $this->url = $value;
+ $this->url = $url;
}
public function _kind(int $value) {
$this->kind = $value;
@@ -502,61 +508,46 @@ class FreshRSS_Feed extends Minz_Model {
$content = html_only_entity_decode($item->get_content());
- if ($item->get_enclosures() != null) {
- $elinks = array();
+ $attributeThumbnail = $item->get_thumbnail() ?? [];
+ if (empty($attributeThumbnail['url'])) {
+ $attributeThumbnail['url'] = '';
+ }
+
+ $attributeEnclosures = [];
+ if (!empty($item->get_enclosures())) {
foreach ($item->get_enclosures() as $enclosure) {
$elink = $enclosure->get_link();
- if ($elink != '' && empty($elinks[$elink])) {
- $content .= '';
-
- if ($enclosure->get_title() != '') {
- $content .= '
' . $enclosure->get_title() . '
';
- }
-
- $enclosureContent = '';
- $elinks[$elink] = true;
+ if ($elink != '') {
+ $etitle = $enclosure->get_title() ?? '';
+ $credit = $enclosure->get_credit() ?? null;
+ $description = $enclosure->get_description() ?? '';
$mime = strtolower($enclosure->get_type() ?? '');
$medium = strtolower($enclosure->get_medium() ?? '');
$height = $enclosure->get_height();
$width = $enclosure->get_width();
$length = $enclosure->get_length();
- if ($medium === 'image' || strpos($mime, 'image') === 0 ||
- ($mime == '' && $length == null && ($width != 0 || $height != 0 || preg_match('/[.](avif|gif|jpe?g|png|svg|webp)$/i', $elink)))) {
- $enclosureContent .= '

';
- } elseif ($medium === 'audio' || strpos($mime, 'audio') === 0) {
- $enclosureContent .= '
💾
';
- } elseif ($medium === 'video' || strpos($mime, 'video') === 0) {
- $enclosureContent .= '
💾
';
- } else { //e.g. application, text, unknown
- $enclosureContent .= '
💾
';
- }
- $thumbnailContent = '';
- if ($enclosure->get_thumbnails() != null) {
+ $attributeEnclosure = [
+ 'url' => $elink,
+ ];
+ if ($etitle != '') $attributeEnclosure['title'] = $etitle;
+ if ($credit != null) $attributeEnclosure['credit'] = $credit->get_name();
+ if ($description != '') $attributeEnclosure['description'] = $description;
+ if ($mime != '') $attributeEnclosure['type'] = $mime;
+ if ($medium != '') $attributeEnclosure['medium'] = $medium;
+ if ($length != '') $attributeEnclosure['length'] = intval($length);
+ if ($height != '') $attributeEnclosure['height'] = intval($height);
+ if ($width != '') $attributeEnclosure['width'] = intval($width);
+
+ if (!empty($enclosure->get_thumbnails())) {
foreach ($enclosure->get_thumbnails() as $thumbnail) {
- if (empty($elinks[$thumbnail])) {
- $elinks[$thumbnail] = true;
- $thumbnailContent .= '

';
+ if ($thumbnail !== $attributeThumbnail['url']) {
+ $attributeEnclosure['thumbnails'][] = $thumbnail;
}
}
}
- $content .= $thumbnailContent;
- $content .= $enclosureContent;
-
- if ($enclosure->get_description() != '') {
- $content .= '
' . $enclosure->get_description() . '
';
- }
- $content .= "
\n";
+ $attributeEnclosures[] = $attributeEnclosure;
}
}
}
@@ -586,6 +577,10 @@ class FreshRSS_Feed extends Minz_Model {
);
$entry->_tags($tags);
$entry->_feed($this);
+ if (!empty($attributeThumbnail['url'])) {
+ $entry->_attributes('thumbnail', $attributeThumbnail);
+ }
+ $entry->_attributes('enclosures', $attributeEnclosures);
$entry->hash(); //Must be computed before loading full content
$entry->loadCompleteContent(); // Optionally load full content for truncated feeds
@@ -596,7 +591,7 @@ class FreshRSS_Feed extends Minz_Model {
/**
* @return SimplePie|null
*/
- public function loadHtmlXpath(bool $loadDetails = false, bool $noCache = false) {
+ public function loadHtmlXpath() {
if ($this->url == '') {
return null;
}
@@ -624,8 +619,9 @@ class FreshRSS_Feed extends Minz_Model {
return null;
}
- $cachePath = FreshRSS_Feed::cacheFilename($feedSourceUrl, $this->attributes(), FreshRSS_Feed::KIND_HTML_XPATH);
- $html = httpGet($feedSourceUrl, $cachePath, 'html', $this->attributes());
+ $cachePath = FreshRSS_Feed::cacheFilename($feedSourceUrl, $this->attributes(), $this->kind());
+ $html = httpGet($feedSourceUrl, $cachePath,
+ $this->kind() === FreshRSS_Feed::KIND_XML_XPATH ? 'xml' : 'html', $this->attributes());
if (strlen($html) <= 0) {
return null;
}
@@ -640,7 +636,18 @@ class FreshRSS_Feed extends Minz_Model {
$doc = new DOMDocument();
$doc->recover = true;
$doc->strictErrorChecking = false;
- $doc->loadHTML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
+
+ switch ($this->kind()) {
+ case FreshRSS_Feed::KIND_HTML_XPATH:
+ $doc->loadHTML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
+ break;
+ case FreshRSS_Feed::KIND_XML_XPATH:
+ $doc->loadXML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
+ break;
+ default:
+ return null;
+ }
+
$xpath = new DOMXPath($doc);
$view->rss_title = $xPathFeedTitle == '' ? $this->name() :
htmlspecialchars(@$xpath->evaluate('normalize-space(' . $xPathFeedTitle . ')'), ENT_COMPAT, 'UTF-8');
@@ -653,7 +660,23 @@ class FreshRSS_Feed extends Minz_Model {
foreach ($nodes as $node) {
$item = [];
$item['title'] = $xPathItemTitle == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemTitle . ')', $node);
- $item['content'] = $xPathItemContent == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemContent . ')', $node);
+
+ $item['content'] = '';
+ if ($xPathItemContent != '') {
+ $result = @$xpath->evaluate($xPathItemContent, $node);
+ if ($result instanceof DOMNodeList) {
+ // List of nodes, save as HTML
+ $content = '';
+ foreach ($result as $child) {
+ $content .= $doc->saveHTML($child) . "\n";
+ }
+ $item['content'] = $content;
+ } else {
+ // Typed expression, save as-is
+ $item['content'] = strval($result);
+ }
+ }
+
$item['link'] = $xPathItemUri == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemUri . ')', $node);
$item['author'] = $xPathItemAuthor == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemAuthor . ')', $node);
$item['timestamp'] = $xPathItemTimestamp == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemTimestamp . ')', $node);
@@ -679,8 +702,15 @@ class FreshRSS_Feed extends Minz_Model {
$item['guid'] = 'urn:sha1:' . sha1($item['title'] . $item['content'] . $item['link']);
}
- if ($item['title'] . $item['content'] . $item['link'] != '') {
- $item = Minz_Helper::htmlspecialchars_utf8($item);
+ if ($item['title'] != '' || $item['content'] != '' || $item['link'] != '') {
+ // HTML-encoding/escaping of the relevant fields (all except 'content')
+ foreach (['author', 'categories', 'guid', 'link', 'thumbnail', 'timestamp', 'title'] as $key) {
+ if (!empty($item[$key])) {
+ $item[$key] = Minz_Helper::htmlspecialchars_utf8($item[$key]);
+ }
+ }
+ // CDATA protection
+ $item['content'] = str_replace(']]>', ']]>', $item['content']);
$view->entries[] = FreshRSS_Entry::fromArray($item);
}
}
@@ -763,8 +793,10 @@ class FreshRSS_Feed extends Minz_Model {
public static function cacheFilename(string $url, array $attributes, int $kind = FreshRSS_Feed::KIND_RSS): string {
$simplePie = customSimplePie($attributes);
$filename = $simplePie->get_cache_filename($url);
- if ($kind == FreshRSS_Feed::KIND_HTML_XPATH) {
+ if ($kind === FreshRSS_Feed::KIND_HTML_XPATH) {
return CACHE_PATH . '/' . $filename . '.html';
+ } elseif ($kind === FreshRSS_Feed::KIND_XML_XPATH) {
+ return CACHE_PATH . '/' . $filename . '.xml';
} else {
return CACHE_PATH . '/' . $filename . '.spc';
}
@@ -966,14 +998,14 @@ class FreshRSS_Feed extends Minz_Model {
$key = $hubJson['key']; //To renew our lease
}
} else {
- @mkdir($path, 0777, true);
+ @mkdir($path, 0770, true);
$key = sha1($path . FreshRSS_Context::$system_conf->salt);
$hubJson = array(
'hub' => $this->hubUrl,
'key' => $key,
);
file_put_contents($hubFilename, json_encode($hubJson));
- @mkdir(PSHB_PATH . '/keys/');
+ @mkdir(PSHB_PATH . '/keys/', 0770, true);
file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', $this->selfUrl);
$text = 'WebSub prepared for ' . $this->url;
Minz_Log::debug($text);
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 5993f50dc..1aae5fee5 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -49,11 +49,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
$values = array(
- substr($valuesTmp['url'], 0, 511),
+ $valuesTmp['url'],
$valuesTmp['kind'] ?? FreshRSS_Feed::KIND_RSS,
$valuesTmp['category'],
mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'),
- substr($valuesTmp['website'], 0, 255),
+ $valuesTmp['website'],
sanitizeHTML($valuesTmp['description'], '', 1023),
$valuesTmp['lastUpdate'],
isset($valuesTmp['priority']) ? intval($valuesTmp['priority']) : FreshRSS_Feed::PRIORITY_MAIN_STREAM,
@@ -434,7 +434,7 @@ SQL;
. '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `_entry` e2 WHERE e2.id_feed=`_feed`.id AND e2.is_read=0)'
. ($id != 0 ? ' WHERE id=:id' : '');
$stm = $this->pdo->prepare($sql);
- if ($id != 0) {
+ if ($stm && $id != 0) {
$stm->bindParam(':id', $id, PDO::PARAM_INT);
}
diff --git a/app/Models/Searchable.php b/app/Models/Searchable.php
index d5bcea49d..a15a44ed7 100644
--- a/app/Models/Searchable.php
+++ b/app/Models/Searchable.php
@@ -2,5 +2,9 @@
interface FreshRSS_Searchable {
+ /**
+ * @param int|string $id
+ * @return Minz_Model
+ */
public function searchById($id);
}
diff --git a/app/Models/SystemConfiguration.php b/app/Models/SystemConfiguration.php
index ec5960c0e..9fc79969d 100644
--- a/app/Models/SystemConfiguration.php
+++ b/app/Models/SystemConfiguration.php
@@ -25,6 +25,10 @@
* @property string $unsafe_autologin_enabled
* @property-read array $trusted_sources
*/
-class FreshRSS_SystemConfiguration extends Minz_Configuration {
+final class FreshRSS_SystemConfiguration extends Minz_Configuration {
+ public static function init($config_filename, $default_filename = null): FreshRSS_SystemConfiguration {
+ parent::register('system', $config_filename, $default_filename);
+ return parent::get('system');
+ }
}
diff --git a/app/Models/Tag.php b/app/Models/Tag.php
index 589648e26..c1290d192 100644
--- a/app/Models/Tag.php
+++ b/app/Models/Tag.php
@@ -5,40 +5,61 @@ class FreshRSS_Tag extends Minz_Model {
* @var int
*/
private $id = 0;
+ /**
+ * @var string
+ */
private $name;
+ /**
+ * @var array
+ */
private $attributes = [];
+ /**
+ * @var int
+ */
private $nbEntries = -1;
+ /**
+ * @var int
+ */
private $nbUnread = -1;
- public function __construct($name = '') {
+ public function __construct(string $name = '') {
$this->_name($name);
}
- public function id() {
+ public function id(): int {
return $this->id;
}
- public function _id($value) {
+ /**
+ * @param int|string $value
+ */
+ public function _id($value): void {
$this->id = (int)$value;
}
- public function name() {
+ public function name(): string {
return $this->name;
}
- public function _name($value) {
+ public function _name(string $value): void {
$this->name = trim($value);
}
- public function attributes($key = '') {
+ /**
+ * @return mixed|string|array|null
+ */
+ public function attributes(string $key = '') {
if ($key == '') {
return $this->attributes;
} else {
- return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
+ return $this->attributes[$key] ?? null;
}
}
- public function _attributes($key, $value) {
+ /**
+ * @param mixed|string|array|null $value
+ */
+ public function _attributes(string $key, $value = null): void {
if ($key == '') {
if (is_string($value)) {
$value = json_decode($value, true);
@@ -53,27 +74,33 @@ class FreshRSS_Tag extends Minz_Model {
}
}
- public function nbEntries() {
+ public function nbEntries(): int {
if ($this->nbEntries < 0) {
$tagDAO = FreshRSS_Factory::createTagDao();
- $this->nbEntries = $tagDAO->countEntries($this->id());
+ $this->nbEntries = $tagDAO->countEntries($this->id()) ?: 0;
}
return $this->nbEntries;
}
- public function _nbEntries($value) {
+ /**
+ * @param string|int $value
+ */
+ public function _nbEntries($value): void {
$this->nbEntries = (int)$value;
}
- public function nbUnread() {
+ public function nbUnread(): int {
if ($this->nbUnread < 0) {
$tagDAO = FreshRSS_Factory::createTagDao();
- $this->nbUnread = $tagDAO->countNotRead($this->id());
+ $this->nbUnread = $tagDAO->countNotRead($this->id()) ?: 0;
}
return $this->nbUnread;
}
- public function _nbUnread($value) {
+ /**
+ * @param string|int$value
+ */
+ public function _nbUnread($value): void {
$this->nbUnread = (int)$value;
}
}
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index f232b2f9f..35123606b 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -267,12 +267,13 @@ SQL;
return $newestItemUsec;
}
+ /** @return int|false */
public function count() {
$sql = 'SELECT COUNT(*) AS count FROM `_tag`';
$stm = $this->pdo->query($sql);
if ($stm !== false) {
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $res[0]['count'];
+ return (int)$res[0]['count'];
} else {
$info = $this->pdo->errorInfo();
if ($this->autoUpdateDb($info)) {
@@ -283,16 +284,27 @@ SQL;
}
}
- public function countEntries($id) {
+ /**
+ * @return int|false
+ */
+ public function countEntries(int $id) {
$sql = 'SELECT COUNT(*) AS count FROM `_entrytag` WHERE id_tag=?';
- $stm = $this->pdo->prepare($sql);
$values = array($id);
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $res[0]['count'];
+ if (($stm = $this->pdo->prepare($sql)) !== false &&
+ $stm->execute($values) &&
+ ($res = $stm->fetchAll(PDO::FETCH_ASSOC)) !== false) {
+ return (int)$res[0]['count'];
+ } else {
+ $info = is_object($stm) ? $stm->errorInfo() : $this->pdo->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
+ }
}
- public function countNotRead($id = null) {
+ /**
+ * @return int|false
+ */
+ public function countNotRead(?int $id = null) {
$sql = 'SELECT COUNT(*) AS count FROM `_entrytag` et '
. 'INNER JOIN `_entry` e ON et.id_entry=e.id '
. 'WHERE e.is_read=0';
@@ -303,11 +315,15 @@ SQL;
$values = [$id];
}
- $stm = $this->pdo->prepare($sql);
-
- $stm->execute($values);
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $res[0]['count'];
+ if (($stm = $this->pdo->prepare($sql)) !== false &&
+ $stm->execute($values) &&
+ ($res = $stm->fetchAll(PDO::FETCH_ASSOC)) !== false) {
+ return (int)$res[0]['count'];
+ } else {
+ $info = is_object($stm) ? $stm->errorInfo() : $this->pdo->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
+ }
}
public function tagEntry($id_tag, $id_entry, $checked = true) {
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index d652ada5b..86125c5f5 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -79,7 +79,6 @@ class FreshRSS_Themes extends Minz_Model {
static $alts = array(
'add' => '➕', //✚
'all' => '☰',
- 'bookmark' => '✨', //★
'bookmark-add' => '➕', //✚
'bookmark-tag' => '📑',
'category' => '🗂️', //☷
diff --git a/app/Models/UserConfiguration.php b/app/Models/UserConfiguration.php
index 05c3c08ac..53b12cc2e 100644
--- a/app/Models/UserConfiguration.php
+++ b/app/Models/UserConfiguration.php
@@ -28,6 +28,7 @@
* @property-read string $is_admin
* @property int|null $keep_history_default
* @property string $language
+ * @property string $timezone
* @property bool $lazyload
* @property string $mail_login
* @property bool $mark_updated_article_unread
@@ -52,6 +53,7 @@
* @property bool $sides_close_article
* @property bool $sticky_post
* @property string $theme
+ * @property string $darkMode
* @property string $token
* @property bool $topline_date
* @property bool $topline_display_authors
@@ -66,6 +68,10 @@
* @property string $view_mode
* @property array $volatile
*/
-class FreshRSS_UserConfiguration extends Minz_Configuration {
+final class FreshRSS_UserConfiguration extends Minz_Configuration {
+ public static function init($config_filename, $default_filename = null, $configuration_setter = null): FreshRSS_UserConfiguration {
+ parent::register('user', $config_filename, $default_filename, $configuration_setter);
+ return parent::get('user');
+ }
}
diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php
index 964324bf7..278074362 100644
--- a/app/Models/UserQuery.php
+++ b/app/Models/UserQuery.php
@@ -8,26 +8,35 @@
*/
class FreshRSS_UserQuery {
+ /** @var bool */
private $deprecated = false;
- private $get;
- private $get_name;
- private $get_type;
- private $name;
- private $order;
+ /** @var string */
+ private $get = '';
+ /** @var string */
+ private $get_name = '';
+ /** @var string */
+ private $get_type = '';
+ /** @var string */
+ private $name = '';
+ /** @var string */
+ private $order = '';
/** @var FreshRSS_BooleanSearch */
private $search;
- private $state;
- private $url;
+ /** @var int */
+ private $state = 0;
+ /** @var string */
+ private $url = '';
+ /** @var FreshRSS_FeedDAO|null */
private $feed_dao;
+ /** @var FreshRSS_CategoryDAO|null */
private $category_dao;
+ /** @var FreshRSS_TagDAO|null */
private $tag_dao;
/**
* @param array $query
- * @param FreshRSS_Searchable $feed_dao
- * @param FreshRSS_Searchable $category_dao
*/
- public function __construct($query, FreshRSS_Searchable $feed_dao = null, FreshRSS_Searchable $category_dao = null, FreshRSS_Searchable $tag_dao = null) {
+ public function __construct(array $query, FreshRSS_FeedDAO $feed_dao = null, FreshRSS_CategoryDAO $category_dao = null, FreshRSS_TagDAO $tag_dao = null) {
$this->category_dao = $category_dao;
$this->feed_dao = $feed_dao;
$this->tag_dao = $tag_dao;
@@ -53,17 +62,17 @@ class FreshRSS_UserQuery {
}
// linked too deeply with the search object, need to use dependency injection
$this->search = new FreshRSS_BooleanSearch($query['search']);
- if (isset($query['state'])) {
- $this->state = $query['state'];
+ if (!empty($query['state'])) {
+ $this->state = intval($query['state']);
}
}
/**
* Convert the current object to an array.
*
- * @return array
+ * @return array
*/
- public function toArray() {
+ public function toArray(): array {
return array_filter(array(
'get' => $this->get,
'name' => $this->name,
@@ -75,29 +84,27 @@ class FreshRSS_UserQuery {
}
/**
- * Parse the get parameter in the query string to extract its name and
- * type
- *
- * @param string $get
+ * Parse the get parameter in the query string to extract its name and type
*/
- private function parseGet($get) {
+ private function parseGet(string $get): void {
$this->get = $get;
if (preg_match('/(?P[acfst])(_(?P\d+))?/', $get, $matches)) {
+ $id = intval($matches['id'] ?? '0');
switch ($matches['type']) {
case 'a':
$this->parseAll();
break;
case 'c':
- $this->parseCategory($matches['id']);
+ $this->parseCategory($id);
break;
case 'f':
- $this->parseFeed($matches['id']);
+ $this->parseFeed($id);
break;
case 's':
$this->parseFavorite();
break;
case 't':
- $this->parseTag($matches['id']);
+ $this->parseTag($id);
break;
}
}
@@ -106,7 +113,7 @@ class FreshRSS_UserQuery {
/**
* Parse the query string when it is an "all" query
*/
- private function parseAll() {
+ private function parseAll(): void {
$this->get_name = 'all';
$this->get_type = 'all';
}
@@ -114,11 +121,10 @@ class FreshRSS_UserQuery {
/**
* Parse the query string when it is a "category" query
*
- * @param integer $id
* @throws FreshRSS_DAO_Exception
*/
- private function parseCategory($id) {
- if (is_null($this->category_dao)) {
+ private function parseCategory(int $id): void {
+ if ($this->category_dao === null) {
throw new FreshRSS_DAO_Exception('Category DAO is not loaded in UserQuery');
}
$category = $this->category_dao->searchById($id);
@@ -133,11 +139,10 @@ class FreshRSS_UserQuery {
/**
* Parse the query string when it is a "feed" query
*
- * @param integer $id
* @throws FreshRSS_DAO_Exception
*/
- private function parseFeed($id) {
- if (is_null($this->feed_dao)) {
+ private function parseFeed(int $id): void {
+ if ($this->feed_dao === null) {
throw new FreshRSS_DAO_Exception('Feed DAO is not loaded in UserQuery');
}
$feed = $this->feed_dao->searchById($id);
@@ -152,10 +157,9 @@ class FreshRSS_UserQuery {
/**
* Parse the query string when it is a "tag" query
*
- * @param integer $id
* @throws FreshRSS_DAO_Exception
*/
- private function parseTag($id) {
+ private function parseTag(int $id): void {
if ($this->tag_dao == null) {
throw new FreshRSS_DAO_Exception('Tag DAO is not loaded in UserQuery');
}
@@ -171,7 +175,7 @@ class FreshRSS_UserQuery {
/**
* Parse the query string when it is a "favorite" query
*/
- private function parseFavorite() {
+ private function parseFavorite(): void {
$this->get_name = 'favorite';
$this->get_type = 'favorite';
}
@@ -180,20 +184,16 @@ class FreshRSS_UserQuery {
* Check if the current user query is deprecated.
* It is deprecated if the category or the feed used in the query are
* not existing.
- *
- * @return boolean
*/
- public function isDeprecated() {
+ public function isDeprecated(): bool {
return $this->deprecated;
}
/**
* Check if the user query has parameters.
* If the type is 'all', it is considered equal to no parameters
- *
- * @return boolean
*/
- public function hasParameters() {
+ public function hasParameters(): bool {
if ($this->get_type === 'all') {
return false;
}
@@ -214,42 +214,40 @@ class FreshRSS_UserQuery {
/**
* Check if there is a search in the search object
- *
- * @return boolean
*/
- public function hasSearch() {
- return $this->search->getRawInput() != "";
+ public function hasSearch(): bool {
+ return $this->search->getRawInput() !== '';
}
- public function getGet() {
+ public function getGet(): string {
return $this->get;
}
- public function getGetName() {
+ public function getGetName(): string {
return $this->get_name;
}
- public function getGetType() {
+ public function getGetType(): string {
return $this->get_type;
}
- public function getName() {
+ public function getName(): string {
return $this->name;
}
- public function getOrder() {
+ public function getOrder(): string {
return $this->order;
}
- public function getSearch() {
+ public function getSearch(): FreshRSS_BooleanSearch {
return $this->search;
}
- public function getState() {
+ public function getState(): int {
return $this->state;
}
- public function getUrl() {
+ public function getUrl(): string {
return $this->url;
}
diff --git a/app/Models/View.php b/app/Models/View.php
index ab1780405..309773c93 100644
--- a/app/Models/View.php
+++ b/app/Models/View.php
@@ -39,6 +39,7 @@ class FreshRSS_View extends Minz_View {
public $details;
public $disable_aside;
public $show_email_field;
+ /** @var string */
public $username;
public $users;
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index d85bd3dc3..b8fff170a 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -18,11 +18,11 @@ ENGINE = INNODB;
CREATE TABLE IF NOT EXISTS `_feed` (
`id` INT NOT NULL AUTO_INCREMENT, -- v0.7
- `url` VARCHAR(511) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ `url` VARCHAR(32768) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`kind` SMALLINT DEFAULT 0, -- 1.20.0
`category` INT DEFAULT 0, -- 1.20.0
`name` VARCHAR(191) NOT NULL,
- `website` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_bin,
+ `website` TEXT CHARACTER SET latin1 COLLATE latin1_bin,
`description` TEXT,
`lastUpdate` INT(11) DEFAULT 0, -- Until year 2038
`priority` TINYINT(2) NOT NULL DEFAULT 10,
@@ -35,7 +35,6 @@ CREATE TABLE IF NOT EXISTS `_feed` (
`cache_nbUnreads` INT DEFAULT 0, -- v0.7
PRIMARY KEY (`id`),
FOREIGN KEY (`category`) REFERENCES `_category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
- UNIQUE KEY (`url`), -- v0.7
INDEX (`name`), -- v0.7
INDEX (`priority`) -- v0.7
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php
index c4da2afad..00a30a8c7 100644
--- a/app/SQL/install.sql.pgsql.php
+++ b/app/SQL/install.sql.pgsql.php
@@ -15,11 +15,11 @@ CREATE TABLE IF NOT EXISTS `_category` (
CREATE TABLE IF NOT EXISTS `_feed` (
"id" SERIAL PRIMARY KEY,
- "url" VARCHAR(511) UNIQUE NOT NULL,
+ "url" VARCHAR(32768) NOT NULL,
"kind" SMALLINT DEFAULT 0, -- 1.20.0
"category" INT DEFAULT 0, -- 1.20.0
"name" VARCHAR(255) NOT NULL,
- "website" VARCHAR(255),
+ "website" VARCHAR(32768),
"description" TEXT,
"lastUpdate" INT DEFAULT 0,
"priority" SMALLINT NOT NULL DEFAULT 10,
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index ccf256d6a..8762b33eb 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -16,11 +16,11 @@ CREATE TABLE IF NOT EXISTS `category` (
CREATE TABLE IF NOT EXISTS `feed` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `url` VARCHAR(511) NOT NULL,
+ `url` VARCHAR(32768) NOT NULL,
`kind` SMALLINT DEFAULT 0, -- 1.20.0
`category` INTEGER DEFAULT 0, -- 1.20.0
`name` VARCHAR(255) NOT NULL,
- `website` VARCHAR(255),
+ `website` VARCHAR(32768),
`description` TEXT,
`lastUpdate` INT(11) DEFAULT 0, -- Until year 2038
`priority` TINYINT(2) NOT NULL DEFAULT 10,
@@ -31,8 +31,7 @@ CREATE TABLE IF NOT EXISTS `feed` (
`attributes` TEXT, -- v1.11.0
`cache_nbEntries` INT DEFAULT 0,
`cache_nbUnreads` INT DEFAULT 0,
- FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
- UNIQUE (`url`)
+ FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
);
CREATE INDEX IF NOT EXISTS feed_name_index ON `feed`(`name`);
CREATE INDEX IF NOT EXISTS feed_priority_index ON `feed`(`priority`);
diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php
index ad0f5f5a8..6b0a3f178 100644
--- a/app/Services/ExportService.php
+++ b/app/Services/ExportService.php
@@ -21,6 +21,7 @@ class FreshRSS_Export_Service {
const FRSS_NAMESPACE = 'https://freshrss.org/opml';
const TYPE_HTML_XPATH = 'HTML+XPath';
+ const TYPE_XML_XPATH = 'XML+XPath';
const TYPE_RSS_ATOM = 'rss';
/**
@@ -43,8 +44,6 @@ class FreshRSS_Export_Service {
* @return array First item is the filename, second item is the content
*/
public function generateOpml() {
- require_once(LIB_PATH . '/lib_opml.php');
-
$view = new FreshRSS_View();
$day = date('Y-m-d');
$view->categories = $this->category_dao->listCategories(true, true);
diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php
index 28286a753..55aa28679 100644
--- a/app/Services/ImportService.php
+++ b/app/Services/ImportService.php
@@ -19,8 +19,6 @@ class FreshRSS_Import_Service {
* @param string $username
*/
public function __construct($username = null) {
- require_once(LIB_PATH . '/lib_opml.php');
-
$this->catDAO = FreshRSS_Factory::createCategoryDao($username);
$this->feedDAO = FreshRSS_Factory::createFeedDao($username);
}
@@ -34,153 +32,194 @@ class FreshRSS_Import_Service {
* This method parses and imports an OPML file.
*
* @param string $opml_file the OPML file content.
- * @param FreshRSS_Category|null $parent_cat the name of the parent category.
- * @param boolean $flatten true to disable categories, false otherwise.
- * @return array|false an array of categories containing some feeds, or false if an error occurred.
+ * @param FreshRSS_Category|null $forced_category force the feeds to be associated to this category.
+ * @param boolean $dry_run true to not create categories and feeds in database.
*/
- public function importOpml(string $opml_file, $parent_cat = null, $flatten = false, $dryRun = false) {
+ public function importOpml(string $opml_file, $forced_category = null, $dry_run = false) {
$this->lastStatus = true;
$opml_array = array();
try {
- $opml_array = libopml_parse_string($opml_file, false);
- } catch (LibOPML_Exception $e) {
- if (FreshRSS_Context::$isCli) {
- fwrite(STDERR, 'FreshRSS error during OPML parsing: ' . $e->getMessage() . "\n");
- } else {
- Minz_Log::warning($e->getMessage());
- }
+ $libopml = new \marienfressinaud\LibOpml\LibOpml(false);
+ $opml_array = $libopml->parseString($opml_file);
+ } catch (\marienfressinaud\LibOpml\Exception $e) {
+ self::log($e->getMessage());
$this->lastStatus = false;
- return false;
+ return;
}
- return $this->addOpmlElements($opml_array['body'], $parent_cat, $flatten, $dryRun);
- }
+ $this->catDAO->checkDefault();
+ $default_category = $this->catDAO->getDefault();
+ if (!$default_category) {
+ self::log('Cannot get the default category');
+ $this->lastStatus = false;
+ return;
+ }
- /**
- * This method imports an OPML file based on its body.
- *
- * @param array $opml_elements an OPML element (body or outline).
- * @param FreshRSS_Category|null $parent_cat the name of the parent category.
- * @param boolean $flatten true to disable categories, false otherwise.
- * @return array an array of categories containing some feeds
- */
- private function addOpmlElements($opml_elements, $parent_cat = null, $flatten = false, $dryRun = false) {
+ // Get the categories by names so we can use this array to retrieve
+ // existing categories later.
+ $categories = $this->catDAO->listCategories(false);
+ $categories_by_names = [];
+ foreach ($categories as $category) {
+ $categories_by_names[$category->name()] = $category;
+ }
+
+ // Get current numbers of categories and feeds, and the limits to
+ // verify the user can import its categories/feeds.
+ $nb_categories = count($categories);
$nb_feeds = count($this->feedDAO->listFeeds());
- $nb_cats = count($this->catDAO->listCategories(false));
$limits = FreshRSS_Context::$system_conf->limits;
- //Sort with categories first
- usort($opml_elements, static function ($a, $b) {
- return strcmp(
- (isset($a['xmlUrl']) ? 'Z' : 'A') . (isset($a['text']) ? $a['text'] : ''),
- (isset($b['xmlUrl']) ? 'Z' : 'A') . (isset($b['text']) ? $b['text'] : ''));
- });
+ // Process the OPML outlines to get a list of categories and a list of
+ // feeds elements indexed by their categories names.
+ list (
+ $categories_elements,
+ $categories_to_feeds,
+ ) = $this->loadFromOutlines($opml_array['body'], '');
- $categories = [];
+ foreach ($categories_to_feeds as $category_name => $feeds_elements) {
+ $category_element = $categories_elements[$category_name] ?? null;
- foreach ($opml_elements as $elt) {
- if (isset($elt['xmlUrl'])) {
- // If xmlUrl exists, it means it is a feed
- if (FreshRSS_Context::$isCli && $nb_feeds >= $limits['max_feeds']) {
- Minz_Log::warning(_t('feedback.sub.feed.over_max',
- $limits['max_feeds']));
+ $category = null;
+ if ($forced_category) {
+ // If the category is forced, ignore the actual category name
+ $category = $forced_category;
+ } elseif (isset($categories_by_names[$category_name])) {
+ // If the category already exists, get it from $categories_by_names
+ $category = $categories_by_names[$category_name];
+ } elseif ($category_element) {
+ // Otherwise, create the category (if possible)
+ $limit_reached = $nb_categories >= $limits['max_categories'];
+ $can_create_category = FreshRSS_Context::$isCli || !$limit_reached;
+
+ if ($can_create_category) {
+ $category = $this->createCategory($category_element, $dry_run);
+ if ($category) {
+ $categories_by_names[$category->name()] = $category;
+ $nb_categories++;
+ }
+ } else {
+ Minz_Log::warning(
+ _t('feedback.sub.category.over_max', $limits['max_categories'])
+ );
+ }
+ }
+
+ if (!$category) {
+ // Category can be null if the feeds weren't in a category
+ // outline, or if we weren't able to create the category.
+ $category = $default_category;
+ }
+
+ // Then, create the feeds one by one and attach them to the
+ // category we just got.
+ foreach ($feeds_elements as $feed_element) {
+ $limit_reached = $nb_feeds >= $limits['max_feeds'];
+ $can_create_feed = FreshRSS_Context::$isCli || !$limit_reached;
+ if (!$can_create_feed) {
+ Minz_Log::warning(
+ _t('feedback.sub.feed.over_max', $limits['max_feeds'])
+ );
$this->lastStatus = false;
- continue;
+ break;
}
- if ($this->addFeedOpml($elt, $parent_cat, $dryRun)) {
+ if ($this->createFeed($feed_element, $category, $dry_run)) {
+ // TODO what if the feed already exists in the database?
$nb_feeds++;
} else {
$this->lastStatus = false;
}
- } elseif (!empty($elt['text'])) {
- // No xmlUrl? It should be a category!
- $limit_reached = !$flatten && ($nb_cats >= $limits['max_categories']);
- if (!FreshRSS_Context::$isCli && $limit_reached) {
- Minz_Log::warning(_t('feedback.sub.category.over_max',
- $limits['max_categories']));
- $this->lastStatus = false;
- $flatten = true;
- }
-
- $category = $this->addCategoryOpml($elt, $parent_cat, $flatten, $dryRun);
-
- if ($category) {
- $nb_cats++;
- $categories[] = $category;
- }
}
}
- return $categories;
+ return;
}
/**
- * This method imports an OPML feed element.
+ * Create a feed from a feed element (i.e. OPML outline).
*
- * @param array $feed_elt an OPML element (must be a feed element).
- * @param FreshRSS_Category|null $parent_cat the name of the parent category.
- * @return FreshRSS_Feed|null a feed.
+ * @param array $feed_elt An OPML element (must be a feed element).
+ * @param FreshRSS_Category $category The category to associate to the feed.
+ * @param boolean $dry_run true to not create the feed in database.
+ *
+ * @return FreshRSS_Feed|null The created feed, or null if it failed.
*/
- private function addFeedOpml($feed_elt, $parent_cat, $dryRun = false) {
- if (empty($feed_elt['xmlUrl'])) {
- return null;
- }
- if ($parent_cat == null) {
- // This feed has no parent category so we get the default one
- $this->catDAO->checkDefault();
- $parent_cat = $this->catDAO->getDefault();
- if ($parent_cat == null) {
- $this->lastStatus = false;
- return null;
- }
- }
-
- // We get different useful information
+ private function createFeed($feed_elt, $category, $dry_run) {
$url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']);
- $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text'] ?? '');
+ $name = $feed_elt['text'] ?? $feed_elt['title'] ?? '';
+ $name = Minz_Helper::htmlspecialchars_utf8($name);
$website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl'] ?? '');
$description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description'] ?? '');
try {
// Create a Feed object and add it in DB
$feed = new FreshRSS_Feed($url);
- $feed->_categoryId($parent_cat->id());
- $parent_cat->addFeed($feed);
+ $feed->_categoryId($category->id());
+ $category->addFeed($feed);
$feed->_name($name);
$feed->_website($website);
$feed->_description($description);
- switch ($feed_elt['type'] ?? '') {
- case FreshRSS_Export_Service::TYPE_HTML_XPATH:
+ switch (strtolower($feed_elt['type'] ?? '')) {
+ case strtolower(FreshRSS_Export_Service::TYPE_HTML_XPATH):
$feed->_kind(FreshRSS_Feed::KIND_HTML_XPATH);
break;
- case FreshRSS_Export_Service::TYPE_RSS_ATOM:
+ case strtolower(FreshRSS_Export_Service::TYPE_XML_XPATH):
+ $feed->_kind(FreshRSS_Feed::KIND_XML_XPATH);
+ break;
+ case strtolower(FreshRSS_Export_Service::TYPE_RSS_ATOM):
default:
$feed->_kind(FreshRSS_Feed::KIND_RSS);
break;
}
- $xPathSettings = [];
- foreach ($feed_elt as $key => $value) {
- if (is_array($value) && !empty($value['value']) && ($value['namespace'] ?? '') === FreshRSS_Export_Service::FRSS_NAMESPACE) {
- switch ($key) {
- case 'cssFullContent': $feed->_pathEntries(Minz_Helper::htmlspecialchars_utf8($value['value'])); break;
- case 'cssFullContentFilter': $feed->_attributes('path_entries_filter', $value['value']); break;
- case 'filtersActionRead': $feed->_filtersAction('read', preg_split('/[\n\r]+/', $value['value'])); break;
- case 'xPathItem': $xPathSettings['item'] = $value['value']; break;
- case 'xPathItemTitle': $xPathSettings['itemTitle'] = $value['value']; break;
- case 'xPathItemContent': $xPathSettings['itemContent'] = $value['value']; break;
- case 'xPathItemUri': $xPathSettings['itemUri'] = $value['value']; break;
- case 'xPathItemAuthor': $xPathSettings['itemAuthor'] = $value['value']; break;
- case 'xPathItemTimestamp': $xPathSettings['itemTimestamp'] = $value['value']; break;
- case 'xPathItemTimeFormat': $xPathSettings['itemTimeFormat'] = $value['value']; break;
- case 'xPathItemThumbnail': $xPathSettings['itemThumbnail'] = $value['value']; break;
- case 'xPathItemCategories': $xPathSettings['itemCategories'] = $value['value']; break;
- case 'xPathItemUid': $xPathSettings['itemUid'] = $value['value']; break;
- }
- }
+ if (isset($feed_elt['frss:cssFullContent'])) {
+ $feed->_pathEntries(Minz_Helper::htmlspecialchars_utf8($feed_elt['frss:cssFullContent']));
}
+
+ if (isset($feed_elt['frss:cssFullContentFilter'])) {
+ $feed->_attributes('path_entries_filter', $feed_elt['frss:cssFullContentFilter']);
+ }
+
+ if (isset($feed_elt['frss:filtersActionRead'])) {
+ $feed->_filtersAction(
+ 'read',
+ preg_split('/[\n\r]+/', $feed_elt['frss:filtersActionRead'])
+ );
+ }
+
+ $xPathSettings = [];
+ if (isset($feed_elt['frss:xPathItem'])) {
+ $xPathSettings['item'] = $feed_elt['frss:xPathItem'];
+ }
+ if (isset($feed_elt['frss:xPathItemTitle'])) {
+ $xPathSettings['itemTitle'] = $feed_elt['frss:xPathItemTitle'];
+ }
+ if (isset($feed_elt['frss:xPathItemContent'])) {
+ $xPathSettings['itemContent'] = $feed_elt['frss:xPathItemContent'];
+ }
+ if (isset($feed_elt['frss:xPathItemUri'])) {
+ $xPathSettings['itemUri'] = $feed_elt['frss:xPathItemUri'];
+ }
+ if (isset($feed_elt['frss:xPathItemAuthor'])) {
+ $xPathSettings['itemAuthor'] = $feed_elt['frss:xPathItemAuthor'];
+ }
+ if (isset($feed_elt['frss:xPathItemTimestamp'])) {
+ $xPathSettings['itemTimestamp'] = $feed_elt['frss:xPathItemTimestamp'];
+ }
+ if (isset($feed_elt['frss:xPathItemTimeFormat'])) {
+ $xPathSettings['itemTimeFormat'] = $feed_elt['frss:xPathItemTimeFormat'];
+ }
+ if (isset($feed_elt['frss:xPathItemThumbnail'])) {
+ $xPathSettings['itemThumbnail'] = $feed_elt['frss:xPathItemThumbnail'];
+ }
+ if (isset($feed_elt['frss:xPathItemCategories'])) {
+ $xPathSettings['itemCategories'] = $feed_elt['frss:xPathItemCategories'];
+ }
+ if (isset($feed_elt['frss:xPathItemUid'])) {
+ $xPathSettings['itemUid'] = $feed_elt['frss:xPathItemUid'];
+ }
+
if (!empty($xPathSettings)) {
$feed->_attributes('xpath', $xPathSettings);
}
@@ -188,9 +227,11 @@ class FreshRSS_Import_Service {
// Call the extension hook
/** @var FreshRSS_Feed|null */
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
- if ($dryRun) {
+
+ if ($dry_run) {
return $feed;
}
+
if ($feed != null) {
// addFeedObject checks if feed is already in DB
$id = $this->feedDAO->addFeedObject($feed);
@@ -202,81 +243,163 @@ class FreshRSS_Import_Service {
}
}
} catch (FreshRSS_Feed_Exception $e) {
- if (FreshRSS_Context::$isCli) {
- fwrite(STDERR, 'FreshRSS error during OPML feed import: ' . $e->getMessage() . "\n");
- } else {
- Minz_Log::warning($e->getMessage());
- }
+ self::log($e->getMessage());
$this->lastStatus = false;
}
- if (FreshRSS_Context::$isCli) {
- fwrite(STDERR, 'FreshRSS error during OPML feed import from URL: ' .
- SimplePie_Misc::url_remove_credentials($url) . ' in category ' . $parent_cat->id() . "\n");
- } else {
- Minz_Log::warning('Error during OPML feed import from URL: ' .
- SimplePie_Misc::url_remove_credentials($url) . ' in category ' . $parent_cat->id());
- }
-
+ $clean_url = SimplePie_Misc::url_remove_credentials($url);
+ self::log("Cannot create {$clean_url} feed in category {$category->name()}");
return null;
}
/**
- * This method imports an OPML category element.
+ * Create and return a category.
*
- * @param array $cat_elt an OPML element (must be a category element).
- * @param FreshRSS_Category|null $parent_cat the name of the parent category.
- * @param boolean $flatten true to disable categories, false otherwise.
- * @return FreshRSS_Category|null a new category containing some feeds, or null if no category was created, or false if an error occurred.
+ * @param array $category_element An OPML element (must be a category element).
+ * @param boolean $dry_run true to not create the category in database.
+ *
+ * @return FreshRSS_Category|null The created category, or null if it failed.
*/
- private function addCategoryOpml($cat_elt, $parent_cat, $flatten = false, $dryRun = false) {
- $error = false;
- $cat = null;
- if (!$flatten) {
- $catName = Minz_Helper::htmlspecialchars_utf8($cat_elt['text']);
- $cat = new FreshRSS_Category($catName);
+ private function createCategory($category_element, $dry_run) {
+ $name = $category_element['text'] ?? $category_element['title'] ?? '';
+ $name = Minz_Helper::htmlspecialchars_utf8($name);
+ $category = new FreshRSS_Category($name);
- foreach ($cat_elt as $key => $value) {
- if (is_array($value) && !empty($value['value']) && ($value['namespace'] ?? '') === FreshRSS_Export_Service::FRSS_NAMESPACE) {
- switch ($key) {
- case 'opmlUrl':
- $opml_url = checkUrl($value['value']);
- if ($opml_url != '') {
- $cat->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
- $cat->_attributes('opml_url', $opml_url);
- }
- break;
- }
- }
+ if (isset($category_element['frss:opmlUrl'])) {
+ $opml_url = checkUrl($category_element['frss:opmlUrl']);
+ if ($opml_url != '') {
+ $category->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
+ $category->_attributes('opml_url', $opml_url);
}
+ }
- if (!$dryRun) {
- $id = $this->catDAO->addCategoryObject($cat);
- if ($id == false) {
- $this->lastStatus = false;
- $error = true;
- } else {
- $cat->_id($id);
+ if ($dry_run) {
+ return $category;
+ }
+
+ $id = $this->catDAO->addCategoryObject($category);
+ if ($id !== false) {
+ $category->_id($id);
+ return $category;
+ } else {
+ self::log("Cannot create category {$category->name()}");
+ $this->lastStatus = false;
+ return null;
+ }
+ }
+
+ /**
+ * Return the list of category and feed outlines by categories names.
+ *
+ * This method is applied to a list of outlines. It merges the different
+ * list of feeds from several outlines into one array.
+ *
+ * @param array $outlines
+ * The outlines from which to extract the outlines.
+ * @param string $parent_category_name
+ * The name of the parent category of the current outlines.
+ *
+ * @return array[]
+ */
+ private function loadFromOutlines($outlines, $parent_category_name) {
+ $categories_elements = [];
+ $categories_to_feeds = [];
+
+ foreach ($outlines as $outline) {
+ // Get the categories and feeds from the child outline (it may
+ // return several categories and feeds if the outline is a category).
+ list (
+ $outline_categories,
+ $outline_categories_to_feeds,
+ ) = $this->loadFromOutline($outline, $parent_category_name);
+
+ // Then, we merge the initial arrays with the arrays returned by
+ // the outline.
+ $categories_elements = array_merge($categories_elements, $outline_categories);
+
+ foreach ($outline_categories_to_feeds as $category_name => $feeds) {
+ if (!isset($categories_to_feeds[$category_name])) {
+ $categories_to_feeds[$category_name] = [];
}
+
+ $categories_to_feeds[$category_name] = array_merge(
+ $categories_to_feeds[$category_name],
+ $feeds
+ );
}
- if ($error) {
- if (FreshRSS_Context::$isCli) {
- fwrite(STDERR, 'FreshRSS error during OPML category import from URL: ' . $catName . "\n");
- } else {
- Minz_Log::warning('Error during OPML category import from URL: ' . $catName);
- }
+ }
+
+ return [$categories_elements, $categories_to_feeds];
+ }
+
+ /**
+ * Return the list of category and feed outlines by categories names.
+ *
+ * This method is applied to a specific outline. If the outline represents
+ * a category (i.e. @outlines key exists), it will reapply loadFromOutlines()
+ * to its children. If the outline represents a feed (i.e. xmlUrl key
+ * exists), it will add the outline to an array accessible by its category
+ * name.
+ *
+ * @param array $outline
+ * The outline from which to extract the categories and feeds outlines.
+ * @param string $parent_category_name
+ * The name of the parent category of the current outline.
+ *
+ * @return array[]
+ */
+ private function loadFromOutline($outline, $parent_category_name) {
+ $categories_elements = [];
+ $categories_to_feeds = [];
+
+ if ($parent_category_name === '' && isset($outline['category'])) {
+ // The outline has no parent category, but its OPML category
+ // attribute is set, so we use it as the category name.
+ // lib_opml parses this attribute as an array of strings, so we
+ // rebuild a string here.
+ $parent_category_name = implode(', ', $outline['category']);
+ $categories_elements[$parent_category_name] = [
+ 'text' => $parent_category_name,
+ ];
+ }
+
+ if (isset($outline['@outlines'])) {
+ // The outline has children, it's probably a category
+ if (!empty($outline['text'])) {
+ $category_name = $outline['text'];
+ } elseif (!empty($outline['title'])) {
+ $category_name = $outline['title'];
} else {
- $parent_cat = $cat;
+ $category_name = $parent_category_name;
}
+
+ list (
+ $categories_elements,
+ $categories_to_feeds,
+ ) = $this->loadFromOutlines($outline['@outlines'], $category_name);
+
+ unset($outline['@outlines']);
+ $categories_elements[$category_name] = $outline;
}
- if (isset($cat_elt['@outlines'])) {
- // Our cat_elt contains more categories or more feeds, so we
- // add them recursively.
- // Note: FreshRSS does not support yet category arborescence, so always flatten from here
- $this->addOpmlElements($cat_elt['@outlines'], $parent_cat, true, $dryRun);
+ // The xmlUrl means it's a feed URL: add the outline to the array if it
+ // exists.
+ if (isset($outline['xmlUrl'])) {
+ if (!isset($categories_to_feeds[$parent_category_name])) {
+ $categories_to_feeds[$parent_category_name] = [];
+ }
+
+ $categories_to_feeds[$parent_category_name][] = $outline;
}
- return $cat;
+ return [$categories_elements, $categories_to_feeds];
+ }
+
+ private static function log($message) {
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, "FreshRSS error during OPML import: {$message}\n");
+ } else {
+ Minz_Log::warning("Error during OPML import: {$message}");
+ }
}
}
diff --git a/app/Utils/feverUtil.php b/app/Utils/feverUtil.php
index a7d21dacb..0e4b712ce 100644
--- a/app/Utils/feverUtil.php
+++ b/app/Utils/feverUtil.php
@@ -1,19 +1,19 @@
salt);
return self::FEVER_PATH . '/.key-' . $salt . '-' . $feverKey . '.txt';
}
/**
* Update the fever key of a user.
- *
- * @param string $username
- * @param string $passwordPlain
* @return string|false the Fever key, or false if the update failed
*/
- public static function updateKey($username, $passwordPlain) {
- $ok = self::checkFeverPath();
- if (!$ok) {
+ public static function updateKey(string $username, string $passwordPlain) {
+ if (!self::checkFeverPath()) {
return false;
}
@@ -48,22 +44,20 @@ class FreshRSS_fever_Util {
$feverKey = strtolower(md5("{$username}:{$passwordPlain}"));
$feverKeyPath = self::getKeyPath($feverKey);
- $res = file_put_contents($feverKeyPath, $username);
- if ($res !== false) {
+ $result = file_put_contents($feverKeyPath, $username);
+ if (is_int($result) && $result > 0) {
return $feverKey;
- } else {
- Minz_Log::warning('Could not save Fever API credentials. Unknown error.', ADMIN_LOG);
- return false;
}
+ Minz_Log::warning('Could not save Fever API credentials. Unknown error.', ADMIN_LOG);
+ return false;
}
/**
* Delete the Fever key of a user.
*
- * @param string $username
- * @return boolean true if the deletion succeeded, else false.
+ * @return bool true if the deletion succeeded, else false.
*/
- public static function deleteKey($username) {
+ public static function deleteKey(string $username) {
$userConfig = get_user_configuration($username);
if ($userConfig === null) {
return false;
diff --git a/app/Utils/passwordUtil.php b/app/Utils/passwordUtil.php
index cff97d2bc..0edead213 100644
--- a/app/Utils/passwordUtil.php
+++ b/app/Utils/passwordUtil.php
@@ -3,26 +3,25 @@
class FreshRSS_password_Util {
// Will also have to be computed client side on mobile devices,
// so do not use a too high cost
- const BCRYPT_COST = 9;
+ public const BCRYPT_COST = 9;
/**
* Return a hash of a plain password, using BCRYPT
- *
- * @param string $passwordPlain
- * @return string
*/
- public static function hash($passwordPlain) {
+ public static function hash(string $passwordPlain): string {
$passwordHash = password_hash(
$passwordPlain,
PASSWORD_BCRYPT,
array('cost' => self::BCRYPT_COST)
);
- $passwordPlain = '';
// Compatibility with bcrypt.js
$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);
- return $passwordHash == '' ? '' : $passwordHash;
+ if ($passwordHash === '' || $passwordHash === null) {
+ return '';
+ }
+ return $passwordHash;
}
/**
@@ -30,11 +29,9 @@ class FreshRSS_password_Util {
*
* A valid password is a string of at least 7 characters.
*
- * @param string $password
- *
- * @return boolean True if the password is valid, false otherwise
+ * @return bool True if the password is valid, false otherwise
*/
- public static function check($password) {
+ public static function check(string $password): bool {
return strlen($password) >= 7;
}
}
diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php
index 7b603555c..4411b5047 100644
--- a/app/i18n/cz/conf.php
+++ b/app/i18n/cz/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Zobrazení',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Spodní řádek',
'display_authors' => 'Autoři',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Časový limit HTML5 oznámení',
),
'show_nav_buttons' => 'Zobrazit navigační tlačítka',
- 'theme' => 'Motiv',
+ 'theme' => array(
+ '_' => 'Motiv',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Motiv „%s“ již není dostupný. Zvolte jiný motiv, prosím.',
'thumbnail' => array(
'label' => 'Náhled',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Na výšku',
'square' => 'Čtverec',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Zobrazení',
'width' => array(
'content' => 'Šířka obsahu',
diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php
index 55586b24c..1f75033fa 100644
--- a/app/i18n/cz/gen.php
+++ b/app/i18n/cz/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Uživatelské dotazy',
'reading' => 'Čtení',
'search' => 'Hledat slova nebo #štítky',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Sdílení',
'shortcuts' => 'Zkratky',
'stats' => 'Statistika',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Známé základní stránky',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Schránka',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'E-mail',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
index a11a9359d..3d08c315b 100644
--- a/app/i18n/cz/sub.php
+++ b/app/i18n/cz/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath pro:',
),
'rss' => 'RSS / Atom (výchozí)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Vymazat mezipaměť',
diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php
index 5e9cdb36e..8962123f4 100644
--- a/app/i18n/de/conf.php
+++ b/app/i18n/de/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Anzeige',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Fußzeile',
'display_authors' => 'Autoren',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Zeitüberschreitung für HTML5-Benachrichtigung',
),
'show_nav_buttons' => 'Zeige Navigations-Buttons',
- 'theme' => 'Erscheinungsbild',
+ 'theme' => array(
+ '_' => 'Layout',
+ 'deprecated' => array(
+ '_' => 'Veraltet',
+ 'description' => 'Diese Layout wird nicht mehr länger aktualisiert und wir in einer zukünftigen Version von FreshRSS entfernt sein.',
+ ),
+ ),
'theme_not_available' => 'Das Erscheinungsbild „%s“ ist nicht mehr verfügbar. Bitte ein anderes auswählen.',
'thumbnail' => array(
'label' => 'Vorschaubild',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Hochformat',
'square' => 'Quadrat',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Anzeige',
'width' => array(
'content' => 'Inhaltsbreite',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index 59f532c74..fb35bc41c 100644
--- a/app/i18n/de/gen.php
+++ b/app/i18n/de/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Benutzerabfragen',
'reading' => 'Lesen',
'search' => 'Suche Worte oder #Tags',
+ 'search_help' => 'Siehe Dokumentation zu den Suchparametern',
'sharing' => 'Teilen',
'shortcuts' => 'Tastaturkürzel',
'stats' => 'Statistiken',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known-Seite (https://withknown.com)',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Zwischenablage',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'E-Mail',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php
index 580f7d348..b265c1b98 100644
--- a/app/i18n/de/sub.php
+++ b/app/i18n/de/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath für:',
),
'rss' => 'RSS / Atom (Standard)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Zwischenspeicher leeren',
diff --git a/app/i18n/el/conf.php b/app/i18n/el/conf.php
index 98f559d18..daacfe684 100644
--- a/app/i18n/el/conf.php
+++ b/app/i18n/el/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Display', // TODO
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Bottom line', // TODO
'display_authors' => 'Authors', // TODO
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 notification timeout', // TODO
),
'show_nav_buttons' => 'Show the navigation buttons', // TODO
- 'theme' => 'Theme', // TODO
+ 'theme' => array(
+ '_' => 'Theme', // TODO
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'The “%s” theme is not available anymore. Please choose another theme.', // TODO
'thumbnail' => array(
'label' => 'Thumbnail', // TODO
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait', // TODO
'square' => 'Square', // TODO
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Display', // TODO
'width' => array(
'content' => 'Content width', // TODO
diff --git a/app/i18n/el/gen.php b/app/i18n/el/gen.php
index a0c95ab39..03852a0c6 100644
--- a/app/i18n/el/gen.php
+++ b/app/i18n/el/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'User queries', // TODO
'reading' => 'Reading', // TODO
'search' => 'Search words or #tags', // TODO
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Sharing', // TODO
'shortcuts' => 'Shortcuts', // TODO
'stats' => 'Statistics', // TODO
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites', // TODO
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // TODO
'blogotext' => 'Blogotext', // TODO
'clipboard' => 'Clipboard', // TODO
'diaspora' => 'Diaspora*', // TODO
'email' => 'Email', // TODO
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // TODO
'gnusocial' => 'GNU social', // TODO
'jdh' => 'Journal du hacker', // TODO
diff --git a/app/i18n/el/sub.php b/app/i18n/el/sub.php
index 424fafc7b..aae9ae412 100644
--- a/app/i18n/el/sub.php
+++ b/app/i18n/el/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath for:', // TODO
),
'rss' => 'RSS / Atom (default)', // TODO
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Clear cache', // TODO
diff --git a/app/i18n/en-us/conf.php b/app/i18n/en-us/conf.php
index 8330e4970..afea0299a 100644
--- a/app/i18n/en-us/conf.php
+++ b/app/i18n/en-us/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Display', // IGNORE
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Bottom line', // IGNORE
'display_authors' => 'Authors', // IGNORE
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 notification timeout', // IGNORE
),
'show_nav_buttons' => 'Show the navigation buttons', // IGNORE
- 'theme' => 'Theme', // IGNORE
+ 'theme' => array(
+ '_' => 'Theme', // IGNORE
+ 'deprecated' => array(
+ '_' => 'Deprecated', // IGNORE
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // IGNORE
+ ),
+ ),
'theme_not_available' => 'The “%s” theme is not available anymore. Please choose another theme.', // IGNORE
'thumbnail' => array(
'label' => 'Thumbnail', // IGNORE
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait', // IGNORE
'square' => 'Square', // IGNORE
),
+ 'timezone' => 'Time zone', // IGNORE
'title' => 'Display', // IGNORE
'width' => array(
'content' => 'Content width', // IGNORE
diff --git a/app/i18n/en-us/gen.php b/app/i18n/en-us/gen.php
index c5f92ad40..ca08ed27f 100644
--- a/app/i18n/en-us/gen.php
+++ b/app/i18n/en-us/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'User queries', // IGNORE
'reading' => 'Reading', // IGNORE
'search' => 'Search words or #tags', // IGNORE
+ 'search_help' => 'See documentation for advanced search parameters', // IGNORE
'sharing' => 'Sharing', // IGNORE
'shortcuts' => 'Shortcuts', // IGNORE
'stats' => 'Statistics', // IGNORE
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites', // IGNORE
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Clipboard', // IGNORE
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/en-us/sub.php b/app/i18n/en-us/sub.php
index a6b311084..92d75b81e 100644
--- a/app/i18n/en-us/sub.php
+++ b/app/i18n/en-us/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath for:', // IGNORE
),
'rss' => 'RSS / Atom (default)', // IGNORE
+ 'xml_xpath' => 'XML + XPath', // IGNORE
),
'maintenance' => array(
'clear_cache' => 'Clear cache', // IGNORE
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index fe03499ea..9899cf897 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Display',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Bottom line',
'display_authors' => 'Authors',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 notification timeout',
),
'show_nav_buttons' => 'Show the navigation buttons',
- 'theme' => 'Theme',
+ 'theme' => array(
+ '_' => 'Theme',
+ 'deprecated' => array(
+ '_' => 'Deprecated',
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS',
+ ),
+ ),
'theme_not_available' => 'The “%s” theme is not available anymore. Please choose another theme.',
'thumbnail' => array(
'label' => 'Thumbnail',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait',
'square' => 'Square',
),
+ 'timezone' => 'Time zone',
'title' => 'Display',
'width' => array(
'content' => 'Content width',
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 8f7065a83..d3a36995f 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'User queries',
'reading' => 'Reading',
'search' => 'Search words or #tags',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Sharing',
'shortcuts' => 'Shortcuts',
'stats' => 'Statistics',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites',
+ 'archiveORG' => 'archive.org',
'archivePH' => 'archive.ph',
'blogotext' => 'Blogotext',
'clipboard' => 'Clipboard',
'diaspora' => 'Diaspora*',
'email' => 'Email',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook',
'gnusocial' => 'GNU social',
'jdh' => 'Journal du hacker',
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index c7e100c25..04caaff05 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath for:',
),
'rss' => 'RSS / Atom (default)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Clear cache',
diff --git a/app/i18n/es/admin.php b/app/i18n/es/admin.php
old mode 100755
new mode 100644
diff --git a/app/i18n/es/conf.php b/app/i18n/es/conf.php
old mode 100755
new mode 100644
index c91b0205c..5137ff987
--- a/app/i18n/es/conf.php
+++ b/app/i18n/es/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Visualización',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Línea inferior',
'display_authors' => 'Autores/Autoras',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Notificación de fin de espera HTML5',
),
'show_nav_buttons' => 'Mostrar los botones de navegación',
- 'theme' => 'Tema',
+ 'theme' => array(
+ '_' => 'Tema',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'El tema “%s” ya no está disponible. Por favor, elija otro tema.',
'thumbnail' => array(
'label' => 'Miniatura',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Retrato',
'square' => 'Cuadrado',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Visualización',
'width' => array(
'content' => 'Ancho de contenido',
diff --git a/app/i18n/es/feedback.php b/app/i18n/es/feedback.php
old mode 100755
new mode 100644
diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php
old mode 100755
new mode 100644
index 209a40dac..5ea2fce23
--- a/app/i18n/es/gen.php
+++ b/app/i18n/es/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Peticiones de usuario',
'reading' => 'Lectura',
'search' => 'Buscar palabras o #etiquetas',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Compartir',
'shortcuts' => 'Atajos',
'stats' => 'Estadísticas',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Sitios basados en conocidos',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Portapapeles',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/es/index.php b/app/i18n/es/index.php
old mode 100755
new mode 100644
diff --git a/app/i18n/es/install.php b/app/i18n/es/install.php
old mode 100755
new mode 100644
diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php
old mode 100755
new mode 100644
index 52d681067..4fd2fa393
--- a/app/i18n/es/sub.php
+++ b/app/i18n/es/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath para:',
),
'rss' => 'RSS / Atom (por defecto)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Borrar caché',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index 61306289c..3122e3be5 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Affichage',
+ 'darkMode' => 'Mode sombre automatique (bêta)',
'icon' => array(
'bottom_line' => 'Ligne du bas',
'display_authors' => 'Auteurs',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Temps d’affichage de la notification HTML5',
),
'show_nav_buttons' => 'Afficher les boutons de navigation',
- 'theme' => 'Thème',
+ 'theme' => array(
+ '_' => 'Thème',
+ 'deprecated' => array(
+ '_' => 'Obsolète',
+ 'description' => 'Ce thème est obsolète et sera supprimé dans une future version de FreshRSS',
+ ),
+ ),
'theme_not_available' => 'Le thème %s n’est plus disponible. Veuillez choisir un autre thème.',
'thumbnail' => array(
'label' => 'Miniature',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait', // IGNORE
'square' => 'Carrée',
),
+ 'timezone' => 'Fuseau horaire',
'title' => 'Affichage',
'width' => array(
'content' => 'Largeur du contenu',
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index 69d260063..53e7160a2 100644
--- a/app/i18n/fr/gen.php
+++ b/app/i18n/fr/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Filtres utilisateurs',
'reading' => 'Lecture',
'search' => 'Rechercher des mots ou des #tags',
+ 'search_help' => 'Voir la documentation pour la syntaxe des recherches avancées',
'sharing' => 'Partage',
'shortcuts' => 'Raccourcis',
'stats' => 'Statistiques',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Sites basés sur Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Presse-papier',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Courriel',
+ 'email-webmail-firefox-fix' => 'Courriel (pour Webmail avec Firefox)',
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php
index f9df0dbcc..be6dc094d 100644
--- a/app/i18n/fr/sub.php
+++ b/app/i18n/fr/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath pour :',
),
'rss' => 'RSS / Atom (par défaut)',
+ 'xml_xpath' => 'XML + XPath', // IGNORE
),
'maintenance' => array(
'clear_cache' => 'Vider le cache',
diff --git a/app/i18n/he/conf.php b/app/i18n/he/conf.php
index ad479db44..c4a490a2d 100644
--- a/app/i18n/he/conf.php
+++ b/app/i18n/he/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'תצוגה',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'שורה תחתונה',
'display_authors' => 'Authors', // TODO
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 התראה פג תוקף',
),
'show_nav_buttons' => 'Show the navigation buttons', // TODO
- 'theme' => 'ערכת נושא',
+ 'theme' => array(
+ '_' => 'ערכת נושא',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'The “%s” theme is not available anymore. Please choose another theme.', // TODO
'thumbnail' => array(
'label' => 'Thumbnail', // TODO
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait', // TODO
'square' => 'Square', // TODO
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'תצוגה',
'width' => array(
'content' => 'רוחב התוכן',
diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php
index a8df3db6b..6345e66e9 100644
--- a/app/i18n/he/gen.php
+++ b/app/i18n/he/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'שאילתות',
'reading' => 'קריאה',
'search' => 'חיפוש מילים או #תגים',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'שיתוף',
'shortcuts' => 'קיצורי דרך',
'stats' => 'סטטיסטיקות',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites', // TODO
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Clipboard', // TODO
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'דואר אלקטרוני',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php
index 25552ffa1..bae5f5177 100644
--- a/app/i18n/he/sub.php
+++ b/app/i18n/he/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath for:', // TODO
),
'rss' => 'RSS / Atom (default)', // TODO
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Clear cache', // TODO
diff --git a/app/i18n/id/conf.php b/app/i18n/id/conf.php
index b8a5b4fc1..8b1fa8dc6 100644
--- a/app/i18n/id/conf.php
+++ b/app/i18n/id/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Display', // TODO
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Bottom line', // TODO
'display_authors' => 'Authors', // TODO
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 notification timeout', // TODO
),
'show_nav_buttons' => 'Show the navigation buttons', // TODO
- 'theme' => 'Theme', // TODO
+ 'theme' => array(
+ '_' => 'Theme', // TODO
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'The “%s” theme is not available anymore. Please choose another theme.', // TODO
'thumbnail' => array(
'label' => 'Thumbnail', // TODO
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portrait', // TODO
'square' => 'Square', // TODO
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Display', // TODO
'width' => array(
'content' => 'Content width', // TODO
diff --git a/app/i18n/id/gen.php b/app/i18n/id/gen.php
index 93f8b0afe..1fc2fa155 100644
--- a/app/i18n/id/gen.php
+++ b/app/i18n/id/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'User queries', // TODO
'reading' => 'Reading', // TODO
'search' => 'Search words or #tags', // TODO
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Sharing', // TODO
'shortcuts' => 'Shortcuts', // TODO
'stats' => 'Statistics', // TODO
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites', // TODO
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // TODO
'blogotext' => 'Blogotext', // TODO
'clipboard' => 'Clipboard', // TODO
'diaspora' => 'Diaspora*', // TODO
'email' => 'Email', // TODO
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // TODO
'gnusocial' => 'GNU social', // TODO
'jdh' => 'Journal du hacker', // TODO
diff --git a/app/i18n/id/sub.php b/app/i18n/id/sub.php
index 7fdf5c024..3f9a4916a 100644
--- a/app/i18n/id/sub.php
+++ b/app/i18n/id/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath for:', // TODO
),
'rss' => 'RSS / Atom (default)', // TODO
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Clear cache', // TODO
diff --git a/app/i18n/it/conf.php b/app/i18n/it/conf.php
index 4597687cc..6f3540322 100644
--- a/app/i18n/it/conf.php
+++ b/app/i18n/it/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Visualizzazione',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Barra in fondo',
'display_authors' => 'Autori',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Notifica timeout HTML5',
),
'show_nav_buttons' => 'Mostra i pulsanti di navigazione',
- 'theme' => 'Tema',
+ 'theme' => array(
+ '_' => 'Tema',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Il tema “%s” non è più disponibile. Si prega di selezionarne un altro.',
'thumbnail' => array(
'label' => 'Miniatura',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Ritratto',
'square' => 'Squadrata',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Visualizzazione',
'width' => array(
'content' => 'Larghezza contenuto',
diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php
index e5458866c..f3edb57aa 100644
--- a/app/i18n/it/gen.php
+++ b/app/i18n/it/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Ricerche personali',
'reading' => 'Lettura',
'search' => 'Ricerca parole o #tags',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Condivisione',
'shortcuts' => 'Comandi tastiera',
'stats' => 'Statistiche',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Siti basati su Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Appunti',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php
index 8614caca7..7ab83cf07 100644
--- a/app/i18n/it/sub.php
+++ b/app/i18n/it/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath per:',
),
'rss' => 'RSS / Atom (predefinito)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Svuota cache',
diff --git a/app/i18n/ja/conf.php b/app/i18n/ja/conf.php
index 5e9aabfa2..4dd939760 100644
--- a/app/i18n/ja/conf.php
+++ b/app/i18n/ja/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => '表示',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => '行の下部',
'display_authors' => '著者',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 の通知タイムアウト時間',
),
'show_nav_buttons' => 'ナビゲーションボタンを表示する',
- 'theme' => 'テーマ',
+ 'theme' => array(
+ '_' => 'テーマ',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => '“%s”テーマはご利用いただけません。他のテーマをお選びください。',
'thumbnail' => array(
'label' => 'サムネイル',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'ポートレート',
'square' => '四角',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'ディスプレイ',
'width' => array(
'content' => 'コンテンツ幅',
diff --git a/app/i18n/ja/gen.php b/app/i18n/ja/gen.php
index 69fc8f9c9..f6cf2dcdf 100644
--- a/app/i18n/ja/gen.php
+++ b/app/i18n/ja/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'ユーザークエリ',
'reading' => 'リーディング',
'search' => '単語で検索するかハッシュタグで検索する',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => '共有',
'shortcuts' => 'ショートカット',
'stats' => '統計',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'よく使われるサイト',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'クリップボード',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Eメール',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/ja/sub.php b/app/i18n/ja/sub.php
index 80548c025..2425b21f3 100644
--- a/app/i18n/ja/sub.php
+++ b/app/i18n/ja/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPathは:',
),
'rss' => 'RSS / Atom (標準)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'キャッシュのクリア',
diff --git a/app/i18n/ko/conf.php b/app/i18n/ko/conf.php
index 279f2f4ad..a88fcf9e0 100644
--- a/app/i18n/ko/conf.php
+++ b/app/i18n/ko/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => '표시',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => '하단',
'display_authors' => '저자',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 알림 타임아웃',
),
'show_nav_buttons' => '내비게이션 버튼 보이기',
- 'theme' => '테마',
+ 'theme' => array(
+ '_' => '테마',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => '“%s” 테마는 더이상 사용할 수 없습니다. 다른 테마를 선택해 주세요.',
'thumbnail' => array(
'label' => '섬네일',
@@ -57,6 +64,7 @@ return array(
'portrait' => '세로 방향',
'square' => '정사각형',
),
+ 'timezone' => 'Time zone', // TODO
'title' => '표시',
'width' => array(
'content' => '내용 표시 너비',
diff --git a/app/i18n/ko/gen.php b/app/i18n/ko/gen.php
index 4f6b6a228..da1f57e9c 100644
--- a/app/i18n/ko/gen.php
+++ b/app/i18n/ko/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => '사용자 쿼리',
'reading' => '읽기',
'search' => '단어 또는 #태그 검색',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => '공유',
'shortcuts' => '단축키',
'stats' => '통계',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known based sites', // IGNORE
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => '클립보드',
'diaspora' => 'Diaspora*', // IGNORE
'email' => '메일',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/ko/sub.php b/app/i18n/ko/sub.php
index e0ef5990b..f376247d5 100644
--- a/app/i18n/ko/sub.php
+++ b/app/i18n/ko/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => '다음의 XPath:',
),
'rss' => 'RSS / Atom (기본값)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => '캐쉬 지우기',
diff --git a/app/i18n/nl/conf.php b/app/i18n/nl/conf.php
index 8b3d597b1..e02ca81cc 100644
--- a/app/i18n/nl/conf.php
+++ b/app/i18n/nl/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Opmaak',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Onderaan',
'display_authors' => 'Auteurs',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 notificatie stop',
),
'show_nav_buttons' => 'Toon navigatieknoppen',
- 'theme' => 'Thema',
+ 'theme' => array(
+ '_' => 'Thema',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Het „%s” thema is niet meer beschikbaar. Kies een ander thema.',
'thumbnail' => array(
'label' => 'Miniatuur',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Staand',
'square' => 'Vierkant',
),
+ 'timezone' => 'Tijdzone',
'title' => 'Opmaak',
'width' => array(
'content' => 'Inhoud breedte',
diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php
index abd21f460..ad3379ece 100644
--- a/app/i18n/nl/gen.php
+++ b/app/i18n/nl/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Gebruikers informatie',
'reading' => 'Lezen',
'search' => 'Zoek woorden of #labels',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Delen',
'shortcuts' => 'Snelle toegang',
'stats' => 'Statistieken',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Known-gebaseerde sites',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Klembord',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php
index 0fa767171..631da9477 100644
--- a/app/i18n/nl/sub.php
+++ b/app/i18n/nl/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath voor:',
),
'rss' => 'RSS / Atom (standaard)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Cache leegmaken',
diff --git a/app/i18n/oc/conf.php b/app/i18n/oc/conf.php
index c1834e9aa..4a3b483e7 100644
--- a/app/i18n/oc/conf.php
+++ b/app/i18n/oc/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Afichatge',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Linha enbàs',
'display_authors' => 'Autors',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Temps d’afichatge de las notificacions HTML5',
),
'show_nav_buttons' => 'Mostrar los botons de navigacion',
- 'theme' => 'Tèma',
+ 'theme' => array(
+ '_' => 'Tèma',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Lo tèma « %s » es pas pus disponible. Causissètz un autre tèma.',
'thumbnail' => array(
'label' => 'Vinheta',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Retrach',
'square' => 'Carrat',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Afichatge',
'width' => array(
'content' => 'Largor del contengut',
diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php
index 41f2c1499..8e852a810 100644
--- a/app/i18n/oc/gen.php
+++ b/app/i18n/oc/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Filtres utilizaire',
'reading' => 'Lectura',
'search' => 'Recercar de mots o d’#etiquetas',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Partatge',
'shortcuts' => 'Acorchis',
'stats' => 'Estatisticas',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Sites basats sus Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Quicha-papiers.',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Corrièl',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php
index 92a73057c..008b4964d 100644
--- a/app/i18n/oc/sub.php
+++ b/app/i18n/oc/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath per :',
),
'rss' => 'RSS / Atom (defaut)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Escafar lo cache',
diff --git a/app/i18n/pl/conf.php b/app/i18n/pl/conf.php
index 31b0d238c..8700a1c13 100644
--- a/app/i18n/pl/conf.php
+++ b/app/i18n/pl/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Wyświetlanie',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Dolny margines',
'display_authors' => 'Autorzy',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Czas wyświetlania powiadomienia HTML5',
),
'show_nav_buttons' => 'Pokaż przyciski nawigacyjne',
- 'theme' => 'Motyw',
+ 'theme' => array(
+ '_' => 'Motyw',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Motyw “%s” nie jest już dostępny. Wybierz inny motyw.',
'thumbnail' => array(
'label' => 'Miniaturka',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portret',
'square' => 'Kwadrat',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Wyświetlanie',
'width' => array(
'content' => 'Rozmiar treści',
diff --git a/app/i18n/pl/gen.php b/app/i18n/pl/gen.php
index 1a7bd69a5..fc91d8bca 100644
--- a/app/i18n/pl/gen.php
+++ b/app/i18n/pl/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Zapisane zapytania',
'reading' => 'Czytanie',
'search' => 'Wyszukaj wyrazy lub #tagi',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Podawanie dalej',
'shortcuts' => 'Skróty klawiszowe',
'stats' => 'Statystyki',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Strony bazujące na usłudze Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Schowek',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'E-mail',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/pl/sub.php b/app/i18n/pl/sub.php
index b6121fcb7..565401982 100644
--- a/app/i18n/pl/sub.php
+++ b/app/i18n/pl/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath dla:',
),
'rss' => 'RSS / Atom (domyślne)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Wyczyść pamięć podręczną',
diff --git a/app/i18n/pt-br/conf.php b/app/i18n/pt-br/conf.php
index b925aee21..f8ad55f14 100644
--- a/app/i18n/pt-br/conf.php
+++ b/app/i18n/pt-br/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Exibição',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Linha inferior',
'display_authors' => 'Autores',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Notificação em HTML5 de timeout',
),
'show_nav_buttons' => 'Mostrar botões de navegação',
- 'theme' => 'Tema',
+ 'theme' => array(
+ '_' => 'Tema',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'O tema “%s” não está mais disponível. Por favor escolha outro tema.',
'thumbnail' => array(
'label' => 'Miniatura',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Modo retrato',
'square' => 'Modo quadrado',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Exibição',
'width' => array(
'content' => 'Largura do conteúdo',
diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php
index 969056969..51c1eb327 100644
--- a/app/i18n/pt-br/gen.php
+++ b/app/i18n/pt-br/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Queries de usuário',
'reading' => 'Leitura',
'search' => 'Procurar por palavras ou #tags',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Compartilhamento',
'shortcuts' => 'Atalhos',
'stats' => 'Estatísticas',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Sites no Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Área de transferência',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'E-mail',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php
index c9755755e..4cdee8681 100644
--- a/app/i18n/pt-br/sub.php
+++ b/app/i18n/pt-br/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath para:',
),
'rss' => 'RSS / Atom (padrão)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Limpar o cache',
diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php
index c0d25aec1..2c5dda544 100644
--- a/app/i18n/ru/conf.php
+++ b/app/i18n/ru/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Отображение',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Нижняя линия',
'display_authors' => 'Авторы',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Таймаут уведомлений HTML5',
),
'show_nav_buttons' => 'Показать кнопки навигации',
- 'theme' => 'Тема',
+ 'theme' => array(
+ '_' => 'Тема',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Тема “%s” больше не доступна. Пожалуйста выберите другю тему.',
'thumbnail' => array(
'label' => 'Эскиз',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Вертикальный',
'square' => 'Квадратный',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Отображение',
'width' => array(
'content' => 'Ширина содержимого',
diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php
index 3ed1ab1ac..ddfea7ca4 100644
--- a/app/i18n/ru/gen.php
+++ b/app/i18n/ru/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Пользовательские запросы',
'reading' => 'Чтение',
'search' => 'Искать слова или #теги',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Обмен',
'shortcuts' => 'Горячие клавиши',
'stats' => 'Статистика',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Сайты на Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Буфер обмена',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Электронная почта',
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php
index 5704b53b1..d13c4c4f0 100644
--- a/app/i18n/ru/sub.php
+++ b/app/i18n/ru/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath для:',
),
'rss' => 'RSS / Atom (по умолчанию)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Очистить кэш',
diff --git a/app/i18n/sk/conf.php b/app/i18n/sk/conf.php
index 7efc3a75d..d4714b506 100644
--- a/app/i18n/sk/conf.php
+++ b/app/i18n/sk/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Zobrazenie',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Spodný riadok',
'display_authors' => 'Autori',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'Limit HTML5 oznámenia',
),
'show_nav_buttons' => 'Zobraziť tlačidlá oznámenia',
- 'theme' => 'Vzhľad',
+ 'theme' => array(
+ '_' => 'Vzhľad',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => 'Vzhľad “%s” už nie je dostupný. Prosím, vyberte si iný vzhľad.',
'thumbnail' => array(
'label' => 'Miniatúra',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Nastojato',
'square' => 'Štvorec',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Zobraziť',
'width' => array(
'content' => 'Šírka obsahu',
diff --git a/app/i18n/sk/gen.php b/app/i18n/sk/gen.php
index 6bb5e4161..d591266e4 100644
--- a/app/i18n/sk/gen.php
+++ b/app/i18n/sk/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Používateľské dopyty',
'reading' => 'Čítanie',
'search' => 'Hľadajte slová alebo #značky',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Zdieľanie',
'shortcuts' => 'Skratky',
'stats' => 'Štatistiky',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Stránky založené na Known',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Schránka',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'E-mail', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/sk/sub.php b/app/i18n/sk/sub.php
index f583f6ca0..3c980d202 100644
--- a/app/i18n/sk/sub.php
+++ b/app/i18n/sk/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath pre:',
),
'rss' => 'RSS / Atom (prednastavené)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Vymazať vyrovnáciu pamäť',
diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php
index 7220d6670..41f658879 100644
--- a/app/i18n/tr/conf.php
+++ b/app/i18n/tr/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => 'Görünüm',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => 'Alt çizgi',
'display_authors' => 'Yazarlar',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 bildirim zaman aşımı',
),
'show_nav_buttons' => 'Gezinti düğmelerini göster',
- 'theme' => 'Tema',
+ 'theme' => array(
+ '_' => 'Tema',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => '“%s” teması şuan uygun değilç Lütfen başka bir tema seçin.',
'thumbnail' => array(
'label' => 'Önizleme',
@@ -57,6 +64,7 @@ return array(
'portrait' => 'Portre',
'square' => 'Kare',
),
+ 'timezone' => 'Time zone', // TODO
'title' => 'Görünüm',
'width' => array(
'content' => 'İçerik genişliği',
diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php
index 8839023e6..4b84d6c40 100644
--- a/app/i18n/tr/gen.php
+++ b/app/i18n/tr/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => 'Kullanıcı sorguları',
'reading' => 'Okuma',
'search' => 'Kelime veya #etiket ara',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => 'Paylaşım',
'shortcuts' => 'Kısayollar',
'stats' => 'İstatistikler',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => 'Bilinen siteler',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => 'Kopyala',
'diaspora' => 'Diaspora*', // IGNORE
'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php
index 056c059ac..3e03f667c 100644
--- a/app/i18n/tr/sub.php
+++ b/app/i18n/tr/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath:',
),
'rss' => 'RSS / Atom (varsayılan)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => 'Önbelleği temizle',
diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php
index 46b4d190a..79bcde2e1 100644
--- a/app/i18n/zh-cn/admin.php
+++ b/app/i18n/zh-cn/admin.php
@@ -17,7 +17,7 @@ return array(
'api_enabled' => '允许 API 访问 (用于手机应用)',
'form' => '网页表单(传统方式, 需要 JavaScript)',
'http' => 'HTTP(面向启用 HTTPS 的高级用户)',
- 'none' => '无认证(危险)',
+ 'none' => '无(危险)',
'title' => '认证',
'token' => '认证口令',
'token_help' => '用于不经认证访问默认用户的 RSS 输出:',
@@ -26,7 +26,7 @@ return array(
),
'check_install' => array(
'cache' => array(
- 'nok' => '请检查 ./data/cache 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data/cache 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'cache 目录权限正常',
),
'categories' => array(
@@ -39,27 +39,27 @@ return array(
),
'ctype' => array(
'nok' => '找不到字符类型检测库(php-ctype)',
- 'ok' => '已找到字符类型检测库 (php-ctype)',
+ 'ok' => '已找到字符类型检测库(ctype)',
),
'curl' => array(
- 'nok' => '找不到 cURL 库(php-cURL)',
- 'ok' => '已找到 cURL 库(php-cURL)',
+ 'nok' => '找不到 cURL 库(php-curl 包)',
+ 'ok' => '已找到 cURL 库',
),
'data' => array(
- 'nok' => '请检查 ./data 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'data 目录权限正常',
),
- 'database' => '数据库相关',
+ 'database' => '数据库安装',
'dom' => array(
- 'nok' => '找不到用于浏览 DOM 的库(php-xml)',
- 'ok' => '已找到用于浏览 DOM 的库(php-xml)',
+ 'nok' => '找不到用于浏览 DOM 的库(php-xml 包)',
+ 'ok' => '已找到用于浏览 DOM 的库',
),
'entries' => array(
'nok' => 'Entry 表配置错误',
- 'ok' => 'Entry 表正常',
+ 'ok' => 'Entry 表配置正常',
),
'favicons' => array(
- 'nok' => '请检查 ./data/favicons 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data/favicons 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'favicons 目录权限正常',
),
'feeds' => array(
@@ -67,46 +67,46 @@ return array(
'ok' => 'Feed 表正常',
),
'fileinfo' => array(
- 'nok' => '找不到 fileinfo 库(php-fileinfo)',
- 'ok' => '已找到 fileinfo 库(php-fileinfo)',
+ 'nok' => '找不到 PHP fileinfo 库(php-fileinfo 包)',
+ 'ok' => '已找到 fileinfo 库',
),
'files' => '文件相关',
'json' => array(
- 'nok' => '找不到 JSON 扩展(php-json )',
- 'ok' => '已找到 JSON 扩展(php-json)',
+ 'nok' => '找不到 JSON 扩展(php-json 包)',
+ 'ok' => '已找到 JSON 扩展',
),
'mbstring' => array(
- 'nok' => '找不到推荐的 Unicode 解析库(mbstring)',
- 'ok' => '已找到推荐的 Unicode 解析库(mbstring)',
+ 'nok' => '找不到推荐用于 Unicode 的 mbstring 库',
+ 'ok' => '已找到推荐用于 Unicode 的 mbstring 库',
),
'pcre' => array(
'nok' => '找不到正则表达式解析库(php-pcre)',
- 'ok' => '已找到正则表达式解析库(php-pcre)',
+ 'ok' => '已找到正则表达式解析库(PCRE)',
),
'pdo' => array(
- 'nok' => '找不到 PDO 或支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
- 'ok' => '已找到 PDO 和支持的至少一种驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
+ 'nok' => '找不到 PDO 或其中一种支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
+ 'ok' => '已找到 PDO 和至少一种支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
),
'php' => array(
- '_' => 'PHP 相关',
+ '_' => 'PHP 安装',
'nok' => '你的 PHP 版本为 %s,但 FreshRSS 最低需要 %s',
'ok' => '你的 PHP 版本为 %s,与 FreshRSS 兼容',
),
'tables' => array(
'nok' => '数据库中缺少一个或多个表',
- 'ok' => '数据库中相关表存在',
+ 'ok' => '数据库中存在正确的表',
),
'title' => '环境检查',
'tokens' => array(
- 'nok' => '请检查 ./data/tokens 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data/tokens 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'tokens 目录权限正常',
),
'users' => array(
- 'nok' => '请检查 ./data/users 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data/users 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'users 目录权限正常',
),
'zip' => array(
- 'nok' => '找不到 ZIP 扩展(php-zip)',
+ 'nok' => '找不到 ZIP 扩展(php-zip 包)',
'ok' => '已找到 ZIP 扩展',
),
),
@@ -119,10 +119,10 @@ return array(
'enabled' => '已启用',
'latest' => '已安装',
'name' => '名称',
- 'no_configure_view' => '此扩展不能配置。',
+ 'no_configure_view' => '此扩展无法配置。',
'system' => array(
'_' => '系统扩展',
- 'no_rights' => '系统扩展(你无权修改)',
+ 'no_rights' => '系统扩展(你没有所需权限)',
),
'title' => '扩展',
'update' => '更新可用',
@@ -130,20 +130,20 @@ return array(
'version' => '版本',
),
'stats' => array(
- '_' => '统计',
+ '_' => '统计数据',
'all_feeds' => '所有订阅源',
'category' => '分类',
'entry_count' => '文章数',
'entry_per_category' => '各分类文章数',
- 'entry_per_day' => '近三十日每日文章数',
- 'entry_per_day_of_week' => '一周各日(平均:%.2f 条消息)',
- 'entry_per_hour' => '各小时(平均:%.2f 条消息)',
- 'entry_per_month' => '各月(平均:%.2f 条消息)',
+ 'entry_per_day' => '每日文章数(近三十日)',
+ 'entry_per_day_of_week' => '一周中(平均:%.2f 条消息)',
+ 'entry_per_hour' => '各小时(平均:%.2f 条消息)',
+ 'entry_per_month' => '各月(平均:%.2f 条消息)',
'entry_repartition' => '文章分布',
'feed' => '订阅源',
'feed_per_category' => '各分类订阅源数',
'idle' => '长期无更新订阅源',
- 'main' => '主要统计',
+ 'main' => '主要统计数据',
'main_stream' => '首页',
'no_idle' => '订阅源近期皆有更新!',
'number_entries' => '%d 篇文章',
@@ -158,9 +158,9 @@ return array(
),
'system' => array(
'_' => '系统配置',
- 'auto-update-url' => '自动升级服务器地址',
+ 'auto-update-url' => '自动更新服务器 URL',
'cookie-duration' => array(
- 'help' => '单位(秒)',
+ 'help' => '单位:秒',
'number' => '保持登录的时长',
),
'force_email_validation' => '强制验证邮箱地址',
@@ -178,8 +178,8 @@ return array(
),
),
'status' => array(
- 'disabled' => '注册表单禁用',
- 'enabled' => '注册表单启用',
+ 'disabled' => '注册表单已禁用',
+ 'enabled' => '注册表单已启用',
),
'title' => '用户注册表单',
),
@@ -191,7 +191,7 @@ return array(
'current_version' => '当前 FreshRSS 版本为 %s。',
'last' => '上次检查:%s',
'none' => '没有可用更新',
- 'title' => '系统更新',
+ 'title' => '更新系统',
),
'user' => array(
'admin' => '管理员',
diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php
index 8f8ef09ad..0be182cfb 100644
--- a/app/i18n/zh-cn/conf.php
+++ b/app/i18n/zh-cn/conf.php
@@ -13,17 +13,17 @@
return array(
'archiving' => array(
'_' => '归档',
- 'exception' => '高级清理策略',
- 'help' => '具体选项位于各订阅源的设置',
- 'keep_favourites' => '不清理已收藏的文章',
- 'keep_labels' => '不清理标签',
+ 'exception' => '清理例外',
+ 'help' => '更多可用选项位于各订阅源的设置',
+ 'keep_favourites' => '永不删除已收藏的文章',
+ 'keep_labels' => '永不删除标签',
'keep_max' => '最多保留的文章数',
'keep_min_by_feed' => '至少保留的文章数',
'keep_period' => '文章最多保留',
- 'keep_unreads' => '不清理未读文章',
- 'maintenance' => '优化',
+ 'keep_unreads' => '永不删除未读文章',
+ 'maintenance' => '维护',
'optimize' => '优化数据库',
- 'optimize_help' => '偶尔执行优化可以减少数据库大小',
+ 'optimize_help' => '偶尔执行可以减少数据库大小',
'policy' => '清理策略',
'policy_warning' => '如果未选择清理策略,则将保留全部文章。',
'purge_now' => '立即清除',
@@ -32,12 +32,13 @@ return array(
),
'display' => array(
'_' => '显示',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => '底栏',
'display_authors' => '作者',
'entry' => '文章图标',
'publication_date' => '更新日期',
- 'related_tags' => '相关标签',
+ 'related_tags' => '文章标签',
'sharing' => '分享',
'summary' => '摘要',
'top_line' => '顶栏',
@@ -48,15 +49,22 @@ return array(
'timeout' => 'HTML5 通知超时时间',
),
'show_nav_buttons' => '显示导航按钮',
- 'theme' => '主题',
+ 'theme' => array(
+ '_' => '主题',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => '“%s” 主题不再可用,请选择其他主题。',
'thumbnail' => array(
'label' => '缩略图',
- 'landscape' => '风景',
+ 'landscape' => '横向',
'none' => '无',
- 'portrait' => '肖像',
- 'square' => '方块',
+ 'portrait' => '纵向',
+ 'square' => '方形',
),
+ 'timezone' => 'Time zone', // TODO
'title' => '显示',
'width' => array(
'content' => '内容宽度',
@@ -80,17 +88,17 @@ return array(
),
),
'profile' => array(
- '_' => '用户管理',
+ '_' => '账户管理',
'api' => 'API 管理',
'delete' => array(
'_' => '账户删除',
- 'warn' => '将删除你的帐户以及所有相关数据!',
+ 'warn' => '你的帐户以及所有相关数据将被删除。',
),
'email' => '邮箱地址',
'password_api' => 'API 密码
(例如用于手机应用)',
'password_form' => '密码
(用于 Web-form 登录方式)',
'password_format' => '至少 7 个字符',
- 'title' => '用户帐户',
+ 'title' => '账户',
),
'query' => array(
'_' => '自定义查询',
@@ -135,67 +143,67 @@ return array(
),
'reading' => array(
'_' => '阅读',
- 'after_onread' => '「全部标记为已读」后',
+ 'after_onread' => '“全部标记为已读”后',
'always_show_favorites' => '默认显示收藏夹中所有的文章',
'article' => array(
'authors_date' => array(
'_' => '作者和日期',
- 'both' => '两者都显示',
- 'footer' => '仅页脚显示',
- 'header' => '仅页眉显示',
+ 'both' => '页脚与页眉',
+ 'footer' => '页脚',
+ 'header' => '页眉',
'none' => '不显示',
),
'feed_name' => array(
- 'above_title' => '在文章标题和标签上方',
+ 'above_title' => '在标题/标签上方',
'none' => '不显示',
'with_authors' => '与作者和日期一行',
),
'feed_title' => '订阅源标题',
'tags' => array(
'_' => '文章标签',
- 'both' => '两者都显示',
- 'footer' => '仅页脚显示',
- 'header' => '仅页眉显示',
+ 'both' => '页脚与页眉',
+ 'footer' => '页脚',
+ 'header' => '页眉',
'none' => '不显示',
),
'tags_max' => array(
'_' => '标签最多显示个数',
- 'help' => '0 标识显示所有标签',
+ 'help' => '0 表示:显示所有标签且不折叠',
),
),
'articles_per_page' => '每页文章数',
'auto_load_more' => '在页面底部载入更多文章',
'auto_remove_article' => '阅读后隐藏文章',
- 'confirm_enabled' => '「全部标记为已读」时显示确认对话框',
+ 'confirm_enabled' => '“全部标记为已读”时显示确认对话框',
'display_articles_unfolded' => '默认展开显示文章',
'display_categories_unfolded' => '展开的分类',
'headline' => array(
'articles' => '文章:打开/关闭',
'articles_header_footer' => '文章: 页眉/页脚',
- 'categories' => '左侧导航:分类',
+ 'categories' => '左侧导航栏:分类',
'mark_as_read' => '标为已读选项',
'misc' => '其它',
'view' => '浏览',
),
- 'hide_read_feeds' => '隐藏没有未读文章的分类和订阅源 (启用「显示所有文章」后不生效)',
+ 'hide_read_feeds' => '隐藏没有未读文章的分类和订阅源(启用“显示所有文章”后不生效)',
'img_with_lazyload' => '延迟加载图片',
'jump_next' => '跳转到下一未读项(订阅源或分类)',
- 'mark_updated_article_unread' => '将更新的文章设为未读',
+ 'mark_updated_article_unread' => '将有更新的文章设为未读',
'number_divided_when_reader' => '阅读视图中显示一半',
'read' => array(
'article_open_on_website' => '在打开原文章后',
'article_viewed' => '在文章被浏览后',
'keep_max_n_unread' => '未读最多保留 n 条',
'scroll' => '在滚动浏览后',
- 'upon_gone' => '在被原订阅源移除后',
+ 'upon_gone' => '在被原订阅源被移除后',
'upon_reception' => '在接收文章后',
'when' => '何时将文章标记为已读',
'when_same_title' => '已存在 n 条相同标题文章',
),
'show' => array(
'_' => '文章显示',
- 'active_category' => '激活的分类',
- 'adaptive' => '智能显示',
+ 'active_category' => '活跃的分类',
+ 'adaptive' => '自适应显示',
'all_articles' => '显示所有',
'all_categories' => '所有分类',
'no_category' => '无分类',
@@ -203,13 +211,13 @@ return array(
'unread' => '只显示未读',
),
'show_fav_unread_help' => '同样适用于标签',
- 'sides_close_article' => '点击文章区域外以关闭',
+ 'sides_close_article' => '点击文章文本区域外关闭文章',
'sort' => array(
'_' => '排列顺序',
'newer_first' => '由新至旧',
'older_first' => '由旧至新',
),
- 'sticky_post' => '打开文章时将其置于页首',
+ 'sticky_post' => '打开文章时将其置顶',
'title' => '阅读',
'view' => array(
'default' => '默认视图',
@@ -222,20 +230,20 @@ return array(
'_' => '分享',
'add' => '添加分享方式',
'blogotext' => 'Blogotext', // IGNORE
- 'deprecated' => '这项功能已废弃并在将来版本的 FreshRSS 中移除,详情请见 说明文档.',
+ 'deprecated' => '此功能已被废弃并会在未来的 FreshRSS 版本中移除,详情见 说明文档.',
'diaspora' => 'Diaspora*', // IGNORE
- 'email' => '邮箱', // IGNORE
- 'facebook' => '脸书', // IGNORE
+ 'email' => 'Email', // IGNORE
+ 'facebook' => 'Facebook', // IGNORE
'more_information' => '更多信息',
'print' => '打印',
'raindrop' => 'Raindrop.io', // IGNORE
'remove' => '删除分享方式',
'shaarli' => 'Shaarli', // IGNORE
- 'share_name' => '名称',
- 'share_url' => '地址',
+ 'share_name' => '显示名称',
+ 'share_url' => '用于分享的 URL',
'title' => '分享',
- 'twitter' => '推特', // IGNORE
- 'wallabag' => 'Wallabag', // IGNORE
+ 'twitter' => 'Twitter', // IGNORE
+ 'wallabag' => 'wallabag', // IGNORE
),
'shortcut' => array(
'_' => '快捷键',
@@ -243,9 +251,9 @@ return array(
'auto_share' => '分享',
'auto_share_help' => '如果有多种分享方式,则会按照它们的序号依次访问。',
'close_dropdown' => '关闭菜单',
- 'collapse_article' => '收起文章',
+ 'collapse_article' => '折叠文章',
'first_article' => '打开第一篇文章',
- 'focus_search' => '聚焦到搜索框',
+ 'focus_search' => '访问搜索框',
'global_view' => '切换到全屏视图',
'help' => '显示帮助文档',
'javascript' => '若要使用快捷键,必须启用 JavaScript',
@@ -254,18 +262,18 @@ return array(
'mark_favorite' => '加入收藏',
'mark_read' => '设为已读',
'navigation' => '浏览',
- 'navigation_help' => '组合 ⇧ Shift 键,浏览快捷键将生效于订阅源。
组合 Alt ⎇ 键,浏览快捷键将生效于分类。',
+ 'navigation_help' => '组合 ⇧ Shift 键,导航快捷键将应用于订阅源。
组合 Alt ⎇ 键,导航快捷键将应用于分类。',
'navigation_no_mod_help' => '以下快捷键不支持组合键(Shift 或 Alt)',
'next_article' => '打开下一篇文章',
'next_unread_article' => '打开下一篇未读文章',
- 'non_standard' => '这些键 (%s) 可能不能作为快捷键',
+ 'non_standard' => '这些键(%s)可能不能作为快捷键',
'normal_view' => '切换到普通视图',
'other_action' => '其他操作',
'previous_article' => '打开上一篇文章',
'reading_view' => '切换到阅读视图',
'rss_view' => '切换到 RSS 视图',
'see_on_website' => '在原网站中查看',
- 'shift_for_all_read' => '组合 Alt ⎇键 将上方的文章标记为已读
组合 ⇧ Shift按键 可以将全部文章设为已读',
+ 'shift_for_all_read' => '+ Alt ⎇ 键将上方的文章标记为已读
+ ⇧ Shift 键将所有文章设为已读',
'skip_next_article' => '跳转到下一篇文章而不打开',
'skip_previous_article' => '跳转到上一篇文章而不打开',
'title' => '快捷键',
@@ -275,7 +283,7 @@ return array(
'views' => '视图',
),
'user' => array(
- 'articles_and_size' => '%s 篇文章 (%s)',
+ 'articles_and_size' => '%s 篇文章(%s)',
'current' => '当前用户',
'is_admin' => '该用户为管理员',
'users' => '用户',
diff --git a/app/i18n/zh-cn/feedback.php b/app/i18n/zh-cn/feedback.php
index 020e70918..701471f4e 100644
--- a/app/i18n/zh-cn/feedback.php
+++ b/app/i18n/zh-cn/feedback.php
@@ -20,8 +20,8 @@ return array(
),
'api' => array(
'password' => array(
- 'failed' => '您的密码无法修改',
- 'updated' => '您的密码已修改',
+ 'failed' => '你的密码无法修改',
+ 'updated' => '你的密码已修改',
),
),
'auth' => array(
@@ -43,7 +43,7 @@ return array(
'already_enabled' => '%s 已启用',
'cannot_remove' => '无法删除 %s',
'disable' => array(
- 'ko' => '禁用 %s 失败。检查 FreshRSS 日志 查看详情。',
+ 'ko' => '无法禁用 %s。检查 FreshRSS 日志 查看详情。',
'ok' => '%s 现已禁用',
),
'enable' => array(
@@ -56,15 +56,15 @@ return array(
'removed' => '%s 已删除',
),
'import_export' => array(
- 'export_no_zip_extension' => '服务器未启用 ZIP 扩展。请尝试逐个导出文件。',
- 'feeds_imported' => '你的订阅已导入,即将刷新',
+ 'export_no_zip_extension' => '服务器未启用 ZIP 扩展,请尝试逐个导出文件。',
+ 'feeds_imported' => '你的订阅源已导入,即将刷新',
'feeds_imported_with_errors' => '你的订阅源已导入,但发生错误',
'file_cannot_be_uploaded' => '文件未能上传!',
'no_zip_extension' => '服务器未启用 ZIP 扩展。',
'zip_error' => '导入 ZIP 文件时出错',
),
'profile' => array(
- 'error' => '你的帐户修改失败',
+ 'error' => '你的帐户无法修改',
'updated' => '你的帐户已修改',
),
'sub' => array(
@@ -79,7 +79,7 @@ return array(
'emptied' => '已清空分类',
'error' => '更新分类失败',
'name_exists' => '分类名已存在',
- 'no_id' => '你必须明确分类编号',
+ 'no_id' => '你必须指定分类 ID',
'no_name' => '分类名不能为空',
'not_delete_default' => '你不能删除默认分类!',
'not_exist' => '分类不存在!',
@@ -94,21 +94,21 @@ return array(
'cache_cleared' => '%s 缓存已清理',
'deleted' => '已删除订阅源',
'error' => '订阅源更新失败',
- 'internal_problem' => '订阅源添加失败。检查 FreshRSS 日志 查看详情。你可以在地址链接后附加 #force_feed 从而尝试强制添加。',
- 'invalid_url' => '地址链接 %s 无效',
+ 'internal_problem' => '订阅源添加失败,检查 FreshRSS 日志 查看详情。你可以在 URL 后添加 #force_feed 尝试强制添加。',
+ 'invalid_url' => 'URL %s 无效',
'n_actualized' => '已更新 %d 个订阅源',
'n_entries_deleted' => '已删除 %d 篇文章',
- 'no_refresh' => '没有可刷新的订阅源…',
+ 'no_refresh' => '没有可刷新的订阅源',
'not_added' => '%s 添加失败',
'not_found' => '无法找到订阅',
'over_max' => '你已达到订阅源数上限(%d)',
- 'reloaded' => '%s 已重置',
+ 'reloaded' => '%s 已重新加载',
'selector_preview' => array(
'http_error' => '无法加载网站内容。',
- 'no_entries' => '您的订阅中没有任何条目。您至少需要一个条目来创建一个预览。',
+ 'no_entries' => '你的订阅中没有任何条目,你至少需要一个条目来创建一个预览。',
'no_feed' => '网络错误(订阅源不存在)',
- 'no_result' => '选择器没有匹配到任何东西。作为备用,原始的feed文本将被显示出来。',
- 'selector_empty' => '选择器是空的。你需要一个来创建预览。',
+ 'no_result' => '选择器没有匹配到任何东西,回退显示原始的订阅源文本。',
+ 'selector_empty' => '选择器是空的,你需要一个来创建预览。',
),
'updated' => '已更新订阅源',
),
@@ -122,10 +122,10 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS 将更新到 版本 %s。',
'error' => '更新出错:%s',
- 'file_is_nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'file_is_nok' => '请检查 %s 目录权限。HTTP 服务器必须拥有写入权限。',
'finished' => '更新完成!',
'none' => '没有可用更新',
- 'server_not_found' => '找不到更新服务器 [%s]',
+ 'server_not_found' => '找不到更新服务器。 [%s]',
),
'user' => array(
'created' => array(
diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php
index 2b2249db5..d4999e5b0 100644
--- a/app/i18n/zh-cn/gen.php
+++ b/app/i18n/zh-cn/gen.php
@@ -12,7 +12,7 @@
return array(
'action' => array(
- 'actualize' => '更新提要',
+ 'actualize' => '更新订阅源',
'add' => '添加',
'back' => '← 返回',
'back_to_rss_feeds' => '← 返回订阅源',
@@ -26,7 +26,7 @@ return array(
'export' => '导出',
'filter' => '过滤',
'import' => '导入',
- 'load_default_shortcuts' => '重置快捷键',
+ 'load_default_shortcuts' => '加载默认快捷键',
'manage' => '管理',
'mark_read' => '标记已读',
'open_url' => '打开链接',
@@ -38,7 +38,7 @@ return array(
'see_website' => '网站中查看',
'submit' => '提交',
'truncate' => '删除所有文章',
- 'update' => '更新订阅',
+ 'update' => '更新',
),
'auth' => array(
'accept_tos' => '我接受 服务条款',
@@ -127,7 +127,7 @@ return array(
'js' => array(
'category_empty' => '清空分类',
'confirm_action' => '你确定要执行此操作吗?这将不可撤销!',
- 'confirm_action_feed_cat' => '你确定要执行此操作吗?你将丢失相关的收藏和自定义查询。这将不可撤销!',
+ 'confirm_action_feed_cat' => '你确定要执行此操作吗?你将丢失相关的收藏和自定义查询,这将不可撤销!',
'feedback' => array(
'body_new_articles' => 'FreshRSS 中有 %%d 篇文章等待阅读。',
'body_unread_articles' => '(未读: %%d)',
@@ -174,13 +174,14 @@ return array(
'queries' => '自定义查询',
'reading' => '阅读',
'search' => '搜索内容或#标签',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => '分享',
'shortcuts' => '快捷键',
'stats' => '统计',
'system' => '系统配置',
'update' => '更新',
'user_management' => '用户管理',
- 'user_profile' => '用户帐户',
+ 'user_profile' => '帐户',
),
'period' => array(
'days' => '天',
@@ -191,12 +192,14 @@ return array(
),
'share' => array(
'Known' => '基于 Known 的站点',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => '剪贴板',
'diaspora' => 'Diaspora*', // IGNORE
- 'email' => '邮箱', // IGNORE
- 'facebook' => '脸书', // IGNORE
+ 'email' => 'Email', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - 兼容 Firefox)',
+ 'facebook' => 'Facebook', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
'lemmy' => 'Lemmy', // IGNORE
@@ -211,10 +214,10 @@ return array(
'raindrop' => 'Raindrop.io', // IGNORE
'reddit' => 'Reddit', // IGNORE
'shaarli' => 'Shaarli', // IGNORE
- 'twitter' => '推特', // IGNORE
+ 'twitter' => 'Twitter', // IGNORE
'wallabag' => 'Wallabag v1', // IGNORE
'wallabagv2' => 'Wallabag v2', // IGNORE
- 'web-sharing-api' => 'Web分享',
+ 'web-sharing-api' => '系统分享',
'whatsapp' => 'Whatsapp', // IGNORE
'xing' => 'Xing', // IGNORE
),
diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php
index 916140107..59d9ffb87 100644
--- a/app/i18n/zh-cn/index.php
+++ b/app/i18n/zh-cn/index.php
@@ -17,7 +17,7 @@ return array(
'bugs_reports' => '报告错误',
'credits' => '致谢',
'credits_content' => '某些设计元素来自于 Bootstrap ,尽管 FreshRSS 并没有使用此框架。图标 来自于 GNOME 项目。Open Sans 字体出自 Steve Matteson 之手。FreshRSS 基于 PHP 框架 Minz。',
- 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 Kriss Feed 或 Leed。 它不仅轻快又易用,而且强大又易于配置。',
+ 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 Kriss Feed 或 Leed。 它不仅轻快易用,并且强大又易于配置。',
'github' => 'Github Issues',
'license' => '授权',
'project_website' => '项目网站',
@@ -25,8 +25,8 @@ return array(
'version' => '版本',
),
'feed' => array(
- 'add' => '你可以添加一些订阅源。',
- 'empty' => '暂时没有文章可显示。',
+ 'add' => '请添加一些订阅源。',
+ 'empty' => '没有文章可以显示。',
'rss_of' => '%s 的订阅源',
'title' => '首页',
'title_fav' => '收藏',
diff --git a/app/i18n/zh-cn/install.php b/app/i18n/zh-cn/install.php
index 8927674d2..1d9d61e38 100644
--- a/app/i18n/zh-cn/install.php
+++ b/app/i18n/zh-cn/install.php
@@ -21,7 +21,7 @@ return array(
'auth' => array(
'form' => '网页表单(传统方式, 依赖 JavaScript)',
'http' => 'HTTP(面向启用 HTTPS 的高级用户)',
- 'none' => '无认证(危险)',
+ 'none' => '无(危险)',
'password_form' => '密码
(用于网页表单登录方式)',
'password_format' => '至少 7 个字符',
'type' => '认证方式',
@@ -30,61 +30,61 @@ return array(
'_' => '数据库',
'conf' => array(
'_' => '数据库配置',
- 'ko' => '请验证你的数据库信息',
+ 'ko' => '验证你的数据库信息',
'ok' => '数据库配置已保存',
),
'host' => '主机',
- 'password' => '密码',
+ 'password' => '数据库密码',
'prefix' => '表前缀',
'type' => '数据库类型',
- 'username' => '用户名',
+ 'username' => '数据库用户名',
),
'check' => array(
'_' => '检查',
'already_installed' => '我们检测到 FreshRSS 已经安装!',
'cache' => array(
- 'nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 %s 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'cache 目录权限正常',
),
'ctype' => array(
'nok' => '找不到字符类型检测库(php-ctype)',
- 'ok' => '已找到字符类型检测库',
+ 'ok' => '已找到字符类型检测库(ctype)',
),
'curl' => array(
- 'nok' => '找不到 cURL 库(php-curl)',
+ 'nok' => '找不到 cURL 库(php-curl 包)',
'ok' => '已找到 cURL 库',
),
'data' => array(
- 'nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 %s 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'data 目录权限正常',
),
'dom' => array(
- 'nok' => '找不到用于浏览 DOM 的库(php-xml)',
+ 'nok' => '找不到用于浏览 DOM 的库(php-xml 包)',
'ok' => '已找到用于浏览 DOM 的库',
),
'favicons' => array(
- 'nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 ./data/favicons 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'favicons 目录权限正常',
),
'fileinfo' => array(
- 'nok' => '找不到 PHP fileinfo 库(php-fileinfo)',
+ 'nok' => '找不到 PHP fileinfo 库(fileinfo 包)',
'ok' => '已找到 fileinfo 库',
),
'json' => array(
- 'nok' => '找不到推荐的 JSON 解析库',
- 'ok' => '已找到推荐的 JSON 解析库',
+ 'nok' => '找不到 JSON 扩展(php-json 包)',
+ 'ok' => '已找到 JSON 扩展',
),
'mbstring' => array(
- 'nok' => '找不到推荐的 Unicode 解析库(mbstring)',
- 'ok' => '已找到推荐的 Unicode 解析库',
+ 'nok' => '找不到推荐用于 Unicode 的 mbstring 库',
+ 'ok' => '已找到推荐用于 Unicode 的 mbstring 库',
),
'pcre' => array(
'nok' => '找不到正则表达式解析库(php-pcre)',
- 'ok' => '已找到正则表达式解析库',
+ 'ok' => '已找到正则表达式解析库(PCRE)',
),
'pdo' => array(
- 'nok' => '找不到 PDO 或支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
- 'ok' => '已找到 PDO 和支持的至少一种驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
+ 'nok' => '找不到 PDO 或其中一种支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
+ 'ok' => '已找到 PDO 和至少一种支持的驱动(pdo_mysql、pdo_sqlite、pdo_pgsql)',
),
'php' => array(
'nok' => '你的 PHP 版本为 %s,但 FreshRSS 最低需要 %s',
@@ -92,12 +92,12 @@ return array(
),
'reload' => '再检查一遍',
'tmp' => array(
- 'nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 %s 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => '缓存目录权限正常。',
),
'unknown_process_username' => '未知',
'users' => array(
- 'nok' => '请检查 %s 目录权限。HTTP 服务器必须有其写入权限。',
+ 'nok' => '请检查 %s 目录权限,HTTP 服务器必须拥有写入权限。',
'ok' => 'users 目录权限正常',
),
'xml' => array(
@@ -114,7 +114,7 @@ return array(
'fix_errors_before' => '请在继续下一步前修复错误',
'javascript_is_better' => '启用 JavaScript 会使 FreshRSS 工作得更好',
'js' => array(
- 'confirm_reinstall' => '重新安装 FreshRSS 将会重置之前的配置。你确定要继续吗?',
+ 'confirm_reinstall' => '重新安装 FreshRSS 将会重置之前的配置,你确定要继续吗?',
),
'language' => array(
'_' => '语言',
diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php
index 4ad401329..5e6e570a9 100644
--- a/app/i18n/zh-cn/sub.php
+++ b/app/i18n/zh-cn/sub.php
@@ -16,9 +16,9 @@ return array(
'title' => 'API', // IGNORE
),
'bookmarklet' => array(
- 'documentation' => '拖动此书签到你的书签栏或者右键选择「收藏此链接」,然后在你想要订阅的页面上点击「订阅」按钮',
+ 'documentation' => '拖动此书签到你的书签栏或者右键选择「收藏此链接」,然后在你想要订阅的页面上点击「订阅」按钮。',
'label' => '订阅',
- 'title' => '书签应用',
+ 'title' => '书签',
),
'category' => array(
'_' => '分类',
@@ -26,18 +26,18 @@ return array(
'archiving' => '归档',
'dynamic_opml' => array(
'_' => '动态订阅',
- 'help' => '使用地址上的 OPML 文件 中的订阅源填充这一分类',
+ 'help' => '使用 URL 上的 OPML 文件 中的订阅源填充这一分类',
),
'empty' => '空分类',
'information' => '信息',
- 'opml_url' => 'OPML 地址',
+ 'opml_url' => 'OPML URL', // IGNORE
'position' => '显示位置',
'position_help' => '控制分类排列顺序',
'title' => '标题',
),
'feed' => array(
'accept_cookies' => '接受 Cookies',
- 'accept_cookies_help' => '允许提要服务器设置 Cookies(仅在请求期间存储在内存中)',
+ 'accept_cookies_help' => '允许订阅源服务器设置 Cookies(仅在请求期间存储在内存中)',
'add' => '添加订阅源',
'advanced' => '高级',
'archiving' => '归档',
@@ -77,8 +77,8 @@ return array(
'html_xpath' => array(
'_' => 'HTML + XPath (Web 抓取)',
'feed_title' => array(
- '_' => '提要标题',
- 'help' => '如 //title 或是静态字符串如 "My custom feed"',
+ '_' => '订阅源标题',
+ 'help' => '如 //title 或是静态字符串如: "My custom feed"',
),
'help' => 'XPath 1.0 是为资深用户准备的标准查询语言,FreshRSS 用以实现 Web 抓取.',
'item' => array(
@@ -99,8 +99,8 @@ return array(
'help' => '例如 descendant::img/@src',
),
'item_timeFormat' => array(
- '_' => 'Custom date/time format', // TODO
- 'help' => 'Optional. A format supported by DateTime::createFromFormat() such as d-m-Y H:i:s', // TODO
+ '_' => '自定义日期/时间格式',
+ 'help' => '可选项, 格式参见 DateTime::createFromFormat() 例如 d-m-Y H:i:s',
),
'item_timestamp' => array(
'_' => '文章日期:',
@@ -111,7 +111,7 @@ return array(
'help' => '注意使用 XPath 轴 descendant::,例如 descendant::h2',
),
'item_uid' => array(
- '_' => '文章唯一标识',
+ '_' => '文章唯一 ID',
'help' => '可选,例如: descendant::div/@data-uri',
),
'item_uri' => array(
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath 定位:',
),
'rss' => 'RSS / Atom (默认)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => '清理缓存',
@@ -175,8 +176,8 @@ return array(
'export_opml' => '导出订阅源列表(OPML)',
'export_starred' => '导出你的收藏',
'feed_list' => '%s 文章列表',
- 'file_to_import' => '需要导入的文件
(OPML、JSON 或 ZIP)',
- 'file_to_import_no_zip' => '需要导入的文件
(OPML 或 JSON)',
+ 'file_to_import' => '需要导入的文件
(OPML、JSON 或 ZIP)',
+ 'file_to_import_no_zip' => '需要导入的文件
(OPML 或 JSON)',
'import' => '导入',
'starred_list' => '收藏文章列表',
'title' => '导入/导出',
diff --git a/app/i18n/zh-cn/user.php b/app/i18n/zh-cn/user.php
index 8b4d35a7f..8a096b985 100644
--- a/app/i18n/zh-cn/user.php
+++ b/app/i18n/zh-cn/user.php
@@ -13,21 +13,21 @@
return array(
'email' => array(
'feedback' => array(
- 'invalid' => '电子邮箱地址无效',
+ 'invalid' => '邮箱地址无效',
'required' => '必须填写邮箱地址',
),
'validation' => array(
- 'change_email' => '您可以在 用户管理 中变更您的邮箱地址',
- 'email_sent_to' => '我们已通过 %s 发送验证邮件给您,请按其中指示来验证邮箱地址。',
+ 'change_email' => '你可以在 用户管理 中变更你的邮箱地址',
+ 'email_sent_to' => '我们已通过 %s 发送验证邮件给你,请按其中指示来验证邮箱地址。',
'feedback' => array(
- 'email_failed' => '由于服务器配置错误,我们无法向您发送邮件。',
- 'email_sent' => '邮件已发送到您的邮箱中',
+ 'email_failed' => '由于服务器配置错误,我们无法向你发送邮件。',
+ 'email_sent' => '邮件已发送到你的邮箱中',
'error' => '邮箱地址无法通过验证',
'ok' => '邮箱地址已成功通过验证',
'unnecessary' => '该邮箱地址已被验证',
'wrong_token' => '由于令牌错误,邮箱地址无法通过验证。',
),
- 'need_to' => '您需要先验证邮箱地址才能使用 %s',
+ 'need_to' => '你需要先验证邮箱地址才能使用 %s',
'resend_email' => '重发邮件',
'title' => '验证邮箱地址',
),
@@ -35,8 +35,8 @@ return array(
'mailer' => array(
'email_need_validation' => array(
'body' => '%s,欢迎',
- 'title' => '您需要验证您的帐户',
- 'welcome' => '您已注册 %s 现在只需点击下方链接通过邮箱验证即可完成注册:',
+ 'title' => '你需要验证你的帐户',
+ 'welcome' => '你已注册 %s 现在只需点击下方链接通过邮箱验证即可完成注册:',
),
),
'password' => array(
@@ -44,7 +44,7 @@ return array(
),
'tos' => array(
'feedback' => array(
- 'invalid' => '您必须接受服务条款才能注册',
+ 'invalid' => '你必须接受服务条款才能注册',
),
),
'username' => array(
diff --git a/app/i18n/zh-tw/conf.php b/app/i18n/zh-tw/conf.php
index 15fabaa40..34439c01b 100644
--- a/app/i18n/zh-tw/conf.php
+++ b/app/i18n/zh-tw/conf.php
@@ -32,6 +32,7 @@ return array(
),
'display' => array(
'_' => '顯示',
+ 'darkMode' => 'Automatic dark mode (beta)', // TODO
'icon' => array(
'bottom_line' => '底欄',
'display_authors' => '作者',
@@ -48,7 +49,13 @@ return array(
'timeout' => 'HTML5 通知超時時間',
),
'show_nav_buttons' => '顯示導航按鈕',
- 'theme' => '主題',
+ 'theme' => array(
+ '_' => '主題',
+ 'deprecated' => array(
+ '_' => 'Deprecated', // TODO
+ 'description' => 'This theme is no longer supported and will be not available anymore in a future release of FreshRSS', // TODO
+ ),
+ ),
'theme_not_available' => '“%s” 主題不再可用,請選擇其他主題。',
'thumbnail' => array(
'label' => '縮圖',
@@ -57,6 +64,7 @@ return array(
'portrait' => '肖像',
'square' => '方塊',
),
+ 'timezone' => 'Time zone', // TODO
'title' => '顯示',
'width' => array(
'content' => '內容寬度',
diff --git a/app/i18n/zh-tw/gen.php b/app/i18n/zh-tw/gen.php
index 1dcd94eeb..3ef8bca44 100644
--- a/app/i18n/zh-tw/gen.php
+++ b/app/i18n/zh-tw/gen.php
@@ -174,6 +174,7 @@ return array(
'queries' => '自定義查詢',
'reading' => '閱讀',
'search' => '搜尋內容或#標簽',
+ 'search_help' => 'See documentation for advanced search parameters', // TODO
'sharing' => '分享',
'shortcuts' => '快捷鍵',
'stats' => '統計',
@@ -191,11 +192,13 @@ return array(
),
'share' => array(
'Known' => '基於 Known 的站點',
+ 'archiveORG' => 'archive.org', // IGNORE
'archivePH' => 'archive.ph', // IGNORE
'blogotext' => 'Blogotext', // IGNORE
'clipboard' => '剪貼板',
'diaspora' => 'Diaspora*', // IGNORE
'email' => '郵箱', // IGNORE
+ 'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)', // TODO
'facebook' => '臉書', // IGNORE
'gnusocial' => 'GNU social', // IGNORE
'jdh' => 'Journal du hacker', // IGNORE
diff --git a/app/i18n/zh-tw/sub.php b/app/i18n/zh-tw/sub.php
index dddcb2661..8a255645d 100644
--- a/app/i18n/zh-tw/sub.php
+++ b/app/i18n/zh-tw/sub.php
@@ -122,6 +122,7 @@ return array(
'xpath' => 'XPath 定位:',
),
'rss' => 'RSS / Atom (默認)',
+ 'xml_xpath' => 'XML + XPath', // TODO
),
'maintenance' => array(
'clear_cache' => '清理暫存',
diff --git a/app/install.php b/app/install.php
index 9d0d855b8..3163367f4 100644
--- a/app/install.php
+++ b/app/install.php
@@ -283,11 +283,7 @@ function freshrss_already_installed() {
// A configuration file already exists, we try to load it.
$system_conf = null;
try {
- Minz_Configuration::register('system', $conf_path);
- /**
- * @var FreshRSS_SystemConfiguration $system_conf
- */
- $system_conf = Minz_Configuration::get('system');
+ $system_conf = FreshRSS_SystemConfiguration::init($conf_path);
} catch (Minz_FileNotExistException $e) {
return false;
}
@@ -295,7 +291,7 @@ function freshrss_already_installed() {
// ok, the global conf exists… but what about default user conf?
$current_user = $system_conf->default_user;
try {
- Minz_Configuration::register('user', join_path(USERS_PATH, $current_user, 'config.php'));
+ FreshRSS_UserConfiguration::init(USERS_PATH . '/' . $current_user . '/config.php');
} catch (Minz_FileNotExistException $e) {
return false;
}
@@ -449,7 +445,7 @@ function printStep1() {
= _t('install.action.fix_errors_before') ?>
-
+
diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml
index 5f1762834..03b8108f7 100644
--- a/app/layout/aside_configure.phtml
+++ b/app/layout/aside_configure.phtml
@@ -1,72 +1,90 @@
diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml
index 3c4f1ec2e..bb9d678dc 100644
--- a/app/layout/aside_feed.phtml
+++ b/app/layout/aside_feed.phtml
@@ -37,7 +37,7 @@