Merge pull request #2180 from wallabag/download-pictures

Download pictures
This commit is contained in:
Jeremy Benoist
2016-11-03 16:22:07 +01:00
committed by GitHub
72 changed files with 1051 additions and 86 deletions

View File

@ -370,7 +370,7 @@ class InstallCommand extends ContainerAwareCommand
],
[
'name' => 'wallabag_url',
'value' => 'http://v2.wallabag.org',
'value' => '',
'section' => 'misc',
],
[
@ -398,6 +398,11 @@ class InstallCommand extends ContainerAwareCommand
'value' => 'wallabag',
'section' => 'misc',
],
[
'name' => 'download_images_enabled',
'value' => '0',
'section' => 'misc',
],
];
foreach ($settings as $setting) {

View File

@ -13,6 +13,8 @@ use Wallabag\CoreBundle\Form\Type\EntryFilterType;
use Wallabag\CoreBundle\Form\Type\EditEntryType;
use Wallabag\CoreBundle\Form\Type\NewEntryType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Wallabag\CoreBundle\Event\EntrySavedEvent;
use Wallabag\CoreBundle\Event\EntryDeletedEvent;
class EntryController extends Controller
{
@ -81,6 +83,9 @@ class EntryController extends Controller
$em->persist($entry);
$em->flush();
// entry saved, dispatch event about it!
$this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
return $this->redirect($this->generateUrl('homepage'));
}
@ -107,6 +112,9 @@ class EntryController extends Controller
$em = $this->getDoctrine()->getManager();
$em->persist($entry);
$em->flush();
// entry saved, dispatch event about it!
$this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
}
return $this->redirect($this->generateUrl('homepage'));
@ -343,6 +351,9 @@ class EntryController extends Controller
$em->persist($entry);
$em->flush();
// entry saved, dispatch event about it!
$this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
}
@ -431,6 +442,9 @@ class EntryController extends Controller
UrlGeneratorInterface::ABSOLUTE_PATH
);
// entry deleted, dispatch event about it!
$this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
$em = $this->getDoctrine()->getManager();
$em->remove($entry);
$em->flush();

View File

@ -140,6 +140,11 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
'value' => 'wallabag',
'section' => 'misc',
],
[
'name' => 'download_images_enabled',
'value' => '0',
'section' => 'misc',
],
];
foreach ($settings as $setting) {
@ -158,6 +163,6 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
*/
public function getOrder()
{
return 50;
return 29;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Wallabag\CoreBundle\Event;
use Symfony\Component\EventDispatcher\Event;
use Wallabag\CoreBundle\Entity\Entry;
/**
* This event is fired as soon as an entry is deleted.
*/
class EntryDeletedEvent extends Event
{
const NAME = 'entry.deleted';
protected $entry;
public function __construct(Entry $entry)
{
$this->entry = $entry;
}
public function getEntry()
{
return $this->entry;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Wallabag\CoreBundle\Event;
use Symfony\Component\EventDispatcher\Event;
use Wallabag\CoreBundle\Entity\Entry;
/**
* This event is fired as soon as an entry was saved.
*/
class EntrySavedEvent extends Event
{
const NAME = 'entry.saved';
protected $entry;
public function __construct(Entry $entry)
{
$this->entry = $entry;
}
public function getEntry()
{
return $this->entry;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\EventListener;
namespace Wallabag\CoreBundle\Event\Listener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

View File

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\EventListener;
namespace Wallabag\CoreBundle\Event\Listener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

View File

@ -0,0 +1,121 @@
<?php
namespace Wallabag\CoreBundle\Event\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Psr\Log\LoggerInterface;
use Wallabag\CoreBundle\Helper\DownloadImages;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Event\EntrySavedEvent;
use Wallabag\CoreBundle\Event\EntryDeletedEvent;
use Doctrine\ORM\EntityManager;
class DownloadImagesSubscriber implements EventSubscriberInterface
{
private $em;
private $downloadImages;
private $enabled;
private $logger;
public function __construct(EntityManager $em, DownloadImages $downloadImages, $enabled, LoggerInterface $logger)
{
$this->em = $em;
$this->downloadImages = $downloadImages;
$this->enabled = $enabled;
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return [
EntrySavedEvent::NAME => 'onEntrySaved',
EntryDeletedEvent::NAME => 'onEntryDeleted',
];
}
/**
* Download images and updated the data into the entry.
*
* @param EntrySavedEvent $event
*/
public function onEntrySaved(EntrySavedEvent $event)
{
if (!$this->enabled) {
$this->logger->debug('DownloadImagesSubscriber: disabled.');
return;
}
$entry = $event->getEntry();
$html = $this->downloadImages($entry);
if (false !== $html) {
$this->logger->debug('DownloadImagesSubscriber: updated html.');
$entry->setContent($html);
}
// update preview picture
$previewPicture = $this->downloadPreviewImage($entry);
if (false !== $previewPicture) {
$this->logger->debug('DownloadImagesSubscriber: update preview picture.');
$entry->setPreviewPicture($previewPicture);
}
$this->em->persist($entry);
$this->em->flush();
}
/**
* Remove images related to the entry.
*
* @param EntryDeletedEvent $event
*/
public function onEntryDeleted(EntryDeletedEvent $event)
{
if (!$this->enabled) {
$this->logger->debug('DownloadImagesSubscriber: disabled.');
return;
}
$this->downloadImages->removeImages($event->getEntry()->getId());
}
/**
* Download all images from the html.
*
* @todo If we want to add async download, it should be done in that method
*
* @param Entry $entry
*
* @return string|false False in case of async
*/
private function downloadImages(Entry $entry)
{
return $this->downloadImages->processHtml(
$entry->getId(),
$entry->getContent(),
$entry->getUrl()
);
}
/**
* Download the preview picture.
*
* @todo If we want to add async download, it should be done in that method
*
* @param Entry $entry
*
* @return string|false False in case of async
*/
private function downloadPreviewImage(Entry $entry)
{
return $this->downloadImages->processSingleImage(
$entry->getId(),
$entry->getPreviewPicture(),
$entry->getUrl()
);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\Subscriber;
namespace Wallabag\CoreBundle\Event\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;

View File

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\Subscriber;
namespace Wallabag\CoreBundle\Event\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;

View File

@ -3,7 +3,7 @@
namespace Wallabag\CoreBundle\Helper;
use Graby\Graby;
use Psr\Log\LoggerInterface as Logger;
use Psr\Log\LoggerInterface;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Tools\Utils;
@ -20,7 +20,7 @@ class ContentProxy
protected $logger;
protected $tagRepository;
public function __construct(Graby $graby, RuleBasedTagger $tagger, TagRepository $tagRepository, Logger $logger)
public function __construct(Graby $graby, RuleBasedTagger $tagger, TagRepository $tagRepository, LoggerInterface $logger)
{
$this->graby = $graby;
$this->tagger = $tagger;
@ -66,6 +66,7 @@ class ContentProxy
$entry->setUrl($content['url'] ?: $url);
$entry->setTitle($title);
$entry->setContent($html);
$entry->setLanguage($content['language']);
$entry->setMimetype($content['content_type']);
$entry->setReadingTime(Utils::getReadingTime($html));

View File

@ -0,0 +1,233 @@
<?php
namespace Wallabag\CoreBundle\Helper;
use Psr\Log\LoggerInterface;
use Symfony\Component\DomCrawler\Crawler;
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser;
use Symfony\Component\Finder\Finder;
class DownloadImages
{
const REGENERATE_PICTURES_QUALITY = 80;
private $client;
private $baseFolder;
private $logger;
private $mimeGuesser;
private $wallabagUrl;
public function __construct(Client $client, $baseFolder, $wallabagUrl, LoggerInterface $logger)
{
$this->client = $client;
$this->baseFolder = $baseFolder;
$this->wallabagUrl = rtrim($wallabagUrl, '/');
$this->logger = $logger;
$this->mimeGuesser = new MimeTypeExtensionGuesser();
$this->setFolder();
}
/**
* Setup base folder where all images are going to be saved.
*/
private function setFolder()
{
// if folder doesn't exist, attempt to create one and store the folder name in property $folder
if (!file_exists($this->baseFolder)) {
mkdir($this->baseFolder, 0777, true);
}
}
/**
* Process the html and extract image from it, save them to local and return the updated html.
*
* @param int $entryId ID of the entry
* @param string $html
* @param string $url Used as a base path for relative image and folder
*
* @return string
*/
public function processHtml($entryId, $html, $url)
{
$crawler = new Crawler($html);
$result = $crawler
->filterXpath('//img')
->extract(array('src'));
$relativePath = $this->getRelativePath($entryId);
// download and save the image to the folder
foreach ($result as $image) {
$imagePath = $this->processSingleImage($entryId, $image, $url, $relativePath);
if (false === $imagePath) {
continue;
}
$html = str_replace($image, $imagePath, $html);
}
return $html;
}
/**
* Process a single image:
* - retrieve it
* - re-saved it (for security reason)
* - return the new local path.
*
* @param int $entryId ID of the entry
* @param string $imagePath Path to the image to retrieve
* @param string $url Url from where the image were found
* @param string $relativePath Relative local path to saved the image
*
* @return string Relative url to access the image from the web
*/
public function processSingleImage($entryId, $imagePath, $url, $relativePath = null)
{
if (null === $relativePath) {
$relativePath = $this->getRelativePath($entryId);
}
$this->logger->debug('DownloadImages: working on image: '.$imagePath);
$folderPath = $this->baseFolder.'/'.$relativePath;
// build image path
$absolutePath = $this->getAbsoluteLink($url, $imagePath);
if (false === $absolutePath) {
$this->logger->error('DownloadImages: Can not determine the absolute path for that image, skipping.');
return false;
}
try {
$res = $this->client->get($absolutePath);
} catch (\Exception $e) {
$this->logger->error('DownloadImages: Can not retrieve image, skipping.', ['exception' => $e]);
return false;
}
$ext = $this->mimeGuesser->guess($res->getHeader('content-type'));
$this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]);
if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) {
$this->logger->error('DownloadImages: Processed image with not allowed extension. Skipping '.$imagePath);
return false;
}
$hashImage = hash('crc32', $absolutePath);
$localPath = $folderPath.'/'.$hashImage.'.'.$ext;
try {
$im = imagecreatefromstring($res->getBody());
} catch (\Exception $e) {
$im = false;
}
if (false === $im) {
$this->logger->error('DownloadImages: Error while regenerating image', ['path' => $localPath]);
return false;
}
switch ($ext) {
case 'gif':
$result = imagegif($im, $localPath);
$this->logger->debug('DownloadImages: Re-creating gif');
break;
case 'jpeg':
case 'jpg':
$result = imagejpeg($im, $localPath, self::REGENERATE_PICTURES_QUALITY);
$this->logger->debug('DownloadImages: Re-creating jpg');
break;
case 'png':
$result = imagepng($im, $localPath, ceil(self::REGENERATE_PICTURES_QUALITY / 100 * 9));
$this->logger->debug('DownloadImages: Re-creating png');
}
imagedestroy($im);
return $this->wallabagUrl.'/assets/images/'.$relativePath.'/'.$hashImage.'.'.$ext;
}
/**
* Remove all images for the given entry id.
*
* @param int $entryId ID of the entry
*/
public function removeImages($entryId)
{
$relativePath = $this->getRelativePath($entryId);
$folderPath = $this->baseFolder.'/'.$relativePath;
$finder = new Finder();
$finder
->files()
->ignoreDotFiles(true)
->in($folderPath);
foreach ($finder as $file) {
@unlink($file->getRealPath());
}
@rmdir($folderPath);
}
/**
* Generate the folder where we are going to save images based on the entry url.
*
* @param int $entryId ID of the entry
*
* @return string
*/
private function getRelativePath($entryId)
{
$hashId = hash('crc32', $entryId);
$relativePath = $hashId[0].'/'.$hashId[1].'/'.$hashId;
$folderPath = $this->baseFolder.'/'.$relativePath;
if (!file_exists($folderPath)) {
mkdir($folderPath, 0777, true);
}
$this->logger->debug('DownloadImages: Folder used for that Entry id', ['folder' => $folderPath, 'entryId' => $entryId]);
return $relativePath;
}
/**
* Make an $url absolute based on the $base.
*
* @see Graby->makeAbsoluteStr
*
* @param string $base Base url
* @param string $url Url to make it absolute
*
* @return false|string
*/
private function getAbsoluteLink($base, $url)
{
if (preg_match('!^https?://!i', $url)) {
// already absolute
return $url;
}
$base = new \SimplePie_IRI($base);
// remove '//' in URL path (causes URLs not to resolve properly)
if (isset($base->ipath)) {
$base->ipath = preg_replace('!//+!', '/', $base->ipath);
}
if ($absolute = \SimplePie_IRI::absolutize($base, $url)) {
return $absolute->get_uri();
}
$this->logger->error('DownloadImages: Can not make an absolute link', ['base' => $base, 'url' => $url]);
return false;
}
}

View File

@ -30,7 +30,7 @@ services:
- "@doctrine"
wallabag_core.subscriber.table_prefix:
class: Wallabag\CoreBundle\Subscriber\TablePrefixSubscriber
class: Wallabag\CoreBundle\Event\Subscriber\TablePrefixSubscriber
arguments:
- "%database_table_prefix%"
tags:
@ -131,8 +131,29 @@ services:
- '%kernel.debug%'
wallabag_core.subscriber.sqlite_cascade_delete:
class: Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber
class: Wallabag\CoreBundle\Event\Subscriber\SQLiteCascadeDeleteSubscriber
arguments:
- "@doctrine"
tags:
- { name: doctrine.event_subscriber }
wallabag_core.subscriber.download_images:
class: Wallabag\CoreBundle\Event\Subscriber\DownloadImagesSubscriber
arguments:
- "@doctrine.orm.default_entity_manager"
- "@wallabag_core.entry.download_images"
- '@=service(''craue_config'').get(''download_images_enabled'')'
- "@logger"
tags:
- { name: kernel.event_subscriber }
wallabag_core.entry.download_images:
class: Wallabag\CoreBundle\Helper\DownloadImages
arguments:
- "@wallabag_core.entry.download_images.client"
- "%kernel.root_dir%/../web/assets/images"
- '@=service(''craue_config'').get(''wallabag_url'')'
- "@logger"
wallabag_core.entry.download_images.client:
class: GuzzleHttp\Client

View File

@ -368,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
# firefox:
# page_title: 'Import > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -505,3 +506,8 @@ flashes:
notice:
# client_created: 'New client created.'
# client_deleted: 'Client deleted'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
how_to: 'Bitte wähle deinen Readability Export aus und klicke den unteren Button für das Hochladen und Importieren dessen.'
worker:
enabled: "Der Import erfolgt asynchron. Sobald der Import gestartet ist, wird diese Aufgabe extern abgearbeitet. Der aktuelle Service dafür ist:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Aus Firefox importieren'
description: "Dieser Import wird all deine Lesezeichen aus Firefox importieren. Gehe zu deinen Lesezeichen (Strg+Shift+O), dann auf \"Importen und Sichern\", wähle \"Sichern…\". Du erhälst eine .json Datei."
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Neuer Client erstellt.'
client_deleted: 'Client gelöscht'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Import > Firefox'
description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -471,6 +472,7 @@ flashes:
rss_updated: 'RSS information updated'
tagging_rules_updated: 'Tagging rules updated'
tagging_rules_deleted: 'Tagging rule deleted'
# user_added: 'User "%username%" added'
rss_token_updated: 'RSS token updated'
annotations_reset: Annotations reset
tags_reset: Tags reset

View File

@ -368,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Importar > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Nuevo cliente creado.'
client_deleted: 'Cliente suprimido'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -285,6 +285,7 @@ quickstart:
paragraph_2: 'ادامه دهید!'
configure:
title: 'برنامه را تنظیم کنید'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
language: 'زبان و نمای برنامه را تغییر دهید'
rss: 'خوراک آر-اس-اس را فعال کنید'
tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید'
@ -367,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'درون‌ریزی > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -504,3 +506,8 @@ flashes:
notice:
# client_created: 'New client created.'
# client_deleted: 'Client deleted'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer."
worker:
enabled: "Les imports sont asynchrones. Une fois l'import commencé un worker externe traitera les messages un par un. Le service activé est :"
download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones."
firefox:
page_title: 'Import > Firefox'
description: "Cet outil va vous permettre d'importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde... ». Vous allez récupérer un fichier .json. </p>"
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Nouveau client %name% créé'
client_deleted: 'Client %name% supprimé'
user:
notice:
added: 'Utilisateur "%username%" ajouté'
updated: 'Utilisateur "%username%" mis à jour'
deleted: 'Utilisateur "%username%" supprimé'

View File

@ -368,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Importa da > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Nuovo client creato.'
client_deleted: 'Client eliminato'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
how_to: "Mercés de seleccionar vòstre Readability fichièr e de clicar sul boton dejós per lo telecargar e l'importar."
worker:
enabled: "L'importacion se fa de manièra asincròna. Un còp l'importacion lançada, una aisina externa s'ocuparà dels messatges un per un. Lo servici actual es : "
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Importar > Firefox'
description: "Aquesta aisina importarà totas vòstres favorits de Firefox. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Novèl client creat'
client_deleted: 'Client suprimit'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
how_to: 'Wybierz swój plik eksportu z Readability i kliknij poniższy przycisk, aby go załadować.'
worker:
enabled: "Import jest wykonywany asynchronicznie. Od momentu rozpoczęcia importu, zewnętrzna usługa może zajmować się na raz tylko jednym zadaniem. Bieżącą usługą jest:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'Import > Firefox'
description: "Ten importer zaimportuje wszystkie twoje zakładki z Firefoksa. Idź do twoich zakładek (Ctrl+Shift+O), następnie w \"Import i kopie zapasowe\", wybierz \"Utwórz kopię zapasową...\". Uzyskasz plik .json."
@ -505,3 +506,8 @@ flashes:
notice:
client_created: 'Nowy klient utworzony.'
client_deleted: 'Klient usunięty'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -368,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
# firefox:
# page_title: 'Import > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -505,3 +506,8 @@ flashes:
notice:
# client_created: 'New client created.'
# client_deleted: 'Client deleted'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'

View File

@ -109,6 +109,7 @@ config:
# if_label: 'if'
# then_tag_as_label: 'then tag as'
# delete_rule_label: 'delete'
# edit_rule_label: 'edit'
rule_label: 'Kural'
tags_label: 'Etiketler'
faq:
@ -367,6 +368,7 @@ import:
# how_to: 'Please select your Readability export and click on the below button to upload and import it.'
worker:
# enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
# download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors."
firefox:
page_title: 'İçe Aktar > Firefox'
# description: "This importer will import all your Firefox bookmarks. Just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file."
@ -504,3 +506,8 @@ flashes:
notice:
# client_created: 'New client created.'
# client_deleted: 'Client deleted'
user:
notice:
# added: 'User "%username%" added'
# updated: 'User "%username%" updated'
# deleted: 'User "%username%" deleted'