Merge branch 'feature/gettext' into develop

This commit is contained in:
Pierre Rudloff 2017-06-01 11:16:07 +02:00
commit 3f1a61df3d
31 changed files with 1702 additions and 407 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ bower_components/
config/config.yml
docs/
clover.xml
i18n/*/LC_MESSAGES/*.mo

View File

@ -90,6 +90,22 @@ module.exports = function (grunt) {
package: {
src: 'package.json'
}
},
potomo: {
dist: {
options: {
poDel: false
},
files: {
'i18n/fr_FR/LC_MESSAGES/Alltube.mo': 'i18n/fr_FR/LC_MESSAGES/Alltube.po',
'i18n/zh_CN/LC_MESSAGES/Alltube.mo': 'i18n/zh_CN/LC_MESSAGES/Alltube.po'
}
}
},
csslint: {
css: {
src: 'css/*'
}
}
}
);
@ -105,9 +121,11 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-phpdocumentor');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-fixpack');
grunt.loadNpmTasks('grunt-potomo');
grunt.loadNpmTasks('grunt-contrib-csslint');
grunt.registerTask('default', ['uglify', 'cssmin']);
grunt.registerTask('lint', ['jslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('default', ['uglify', 'cssmin', 'potomo']);
grunt.registerTask('lint', ['jslint', 'csslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('test', ['phpunit']);
grunt.registerTask('doc', ['phpdocumentor']);
grunt.registerTask('release', ['default', 'githash', 'compress']);

View File

@ -38,7 +38,7 @@ class Config
*
* @var array
*/
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist'];
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist', '--restrict-filenames'];
/**
* Enable audio conversion.

95
classes/Locale.php Normal file
View File

@ -0,0 +1,95 @@
<?php
/**
* Locale class.
*/
namespace Alltube;
use Teto\HTTP\AcceptLanguage;
/**
* Class used to represent locales.
*/
class Locale
{
/**
* Locale language.
*
* @var string
*/
private $language;
/**
* Locale region.
*
* @var string
*/
private $region;
/**
* Locale constructor.
*
* @param string $locale ISO 15897 code
*/
public function __construct($locale)
{
$parse = AcceptLanguage::parse($locale);
$this->language = $parse[1]['language'];
$this->region = $parse[1]['region'];
}
/**
* Convert the locale to a string.
*
* @return string ISO 15897 code
*/
public function __toString()
{
return $this->getIso15897();
}
/**
* Get the full name of the locale.
*
* @return string
*/
public function getFullName()
{
return \Locale::getDisplayName($this->getIso15897(), $this->getIso15897());
}
/**
* Get the ISO 15897 code.
*
* @return string
*/
public function getIso15897()
{
return $this->language.'_'.$this->region;
}
/**
* Get the BCP 47 code.
*
* @return string
*/
public function getBcp47()
{
return $this->language.'-'.$this->region;
}
/**
* Get the ISO 3166 code.
*
* @return string
*/
public function getIso3166()
{
return strtolower($this->region);
}
public function getCountry()
{
return country($this->getIso3166());
}
}

87
classes/LocaleManager.php Normal file
View File

@ -0,0 +1,87 @@
<?php
/**
* LocaleManager class.
*/
namespace Alltube;
/**
* Class used to manage locales.
*/
class LocaleManager
{
/**
* Supported locales.
*
* @var array
*/
private $supportedLocales = ['en_US', 'fr_FR', 'zh_CN'];
/**
* Current locale.
*
* @var Locale
*/
private $curLocale;
/**
* Session segment used to store session variables.
*
* @var \Aura\Session\Segment
*/
private $sessionSegment;
/**
* LocaleManager constructor.
*
* @param array $cookies Cookie array
*/
public function __construct(array $cookies = [])
{
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\LocaleManager');
$cookieLocale = $this->sessionSegment->get('locale');
if (isset($cookieLocale)) {
$this->setLocale(new Locale($cookieLocale));
}
}
/**
* Get a list of supported locales.
*
* @return Locale[]
*/
public function getSupportedLocales()
{
$return = [];
foreach ($this->supportedLocales as $supportedLocale) {
$return[] = new Locale($supportedLocale);
}
return $return;
}
/**
* Get the current locale.
*
* @return Locale
*/
public function getLocale()
{
return $this->curLocale;
}
/**
* Set the current locale.
*
* @param Locale $locale Locale
*/
public function setLocale(Locale $locale)
{
putenv('LANG='.$locale);
setlocale(LC_ALL, [$locale, $locale.'.utf8']);
$this->curLocale = $locale;
$this->sessionSegment->set('locale', $locale);
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* LocaleMiddleware class.
*/
namespace Alltube;
use Psr\Container\ContainerInterface;
use Slim\Http\Request;
use Slim\Http\Response;
use Teto\HTTP\AcceptLanguage;
/**
* Detect user locale.
*/
class LocaleMiddleware
{
/**
* LocaleManager instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* LocaleMiddleware constructor.
*
* @param ContainerInterface $container Slim dependency container
*/
public function __construct(ContainerInterface $container)
{
$this->localeManager = $container->get('locale');
}
/**
* Test if a locale can be used for the current user.
*
* @param array $proposedLocale Locale array created by AcceptLanguage::parse()
*
* @return string Locale name if chosen, nothing otherwise
*/
public function testLocale(array $proposedLocale)
{
foreach ($this->localeManager->getSupportedLocales() as $locale) {
$parsedLocale = AcceptLanguage::parse($locale);
if (isset($proposedLocale['language'])
&& $parsedLocale[1]['language'] == $proposedLocale['language']
&& $parsedLocale[1]['region'] == $proposedLocale['region']
) {
return new Locale($proposedLocale['language'].'_'.$proposedLocale['region']);
}
}
}
/**
* Main middleware function.
*
* @param Request $request PSR request
* @param Response $response PSR response
* @param callable $next Next middleware
*
* @return Response
*/
public function __invoke(Request $request, Response $response, callable $next)
{
$headers = $request->getHeader('Accept-Language');
$curLocale = $this->localeManager->getLocale();
if (!isset($curLocale)) {
if (isset($headers[0])) {
$this->localeManager->setLocale(
AcceptLanguage::detect([$this, 'testLocale'], new Locale('en_US'), $headers[0])
);
} else {
$this->localeManager->setLocale(new Locale('en_US'));
}
}
return $next($request, $response);
}
}

View File

@ -13,7 +13,10 @@
"ptachoire/process-builder-chain": "~1.2.0",
"guzzlehttp/guzzle": "~6.2.0",
"aura/session": "~2.1.0",
"barracudanetworks/archivestream-php": "~1.0.5"
"barracudanetworks/archivestream-php": "~1.0.5",
"smarty-gettext/smarty-gettext": "~1.5.1",
"zonuexe/http-accept-language": "~0.4.1",
"rinvex/country": "~2.0.0"
},
"require-dev": {
"symfony/var-dumper": "~3.2.0",

201
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "73d6ed72dae93290d21ac47042e14886",
"content-hash": "22e16312bcf339c90850660d9e5923e7",
"packages": [
{
"name": "aura/session",
@ -591,6 +591,80 @@
"description": "Add ability to chain symfony processes",
"time": "2016-04-10T08:33:20+00:00"
},
{
"name": "rinvex/country",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/rinvex/country.git",
"reference": "9405da035644bc76671bfba0c282fef8fd3408e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rinvex/country/zipball/9405da035644bc76671bfba0c282fef8fd3408e1",
"reference": "9405da035644bc76671bfba0c282fef8fd3408e1",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"phpunit/phpunit": "^5.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Rinvex\\Country\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rinvex LLC",
"email": "help@rinvex.com",
"homepage": "https://rinvex.com"
},
{
"name": "Abdelrahman Omran",
"email": "me@omranic.com",
"homepage": "https://omranic.com",
"role": "Project Lead"
},
{
"name": "The Generous Laravel Community",
"homepage": "https://github.com/rinvex/country/contributors"
}
],
"description": "Rinvex Country is a simple and lightweight package for retrieving country details with flexibility. A whole bunch of data including name, demonym, capital, iso codes, dialling codes, geo data, currencies, flags, emoji, and other attributes for all 250 countries worldwide at your fingertips.",
"homepage": "https://rinvex.com",
"keywords": [
"Flexible",
"Simple",
"countries",
"country",
"currencies",
"demonym",
"dialling",
"emoji",
"flags",
"geographic",
"languages",
"rinvex",
"svg"
],
"time": "2016-11-27T05:53:11+00:00"
},
{
"name": "slim/slim",
"version": "3.8.1",
@ -662,6 +736,60 @@
],
"time": "2017-03-19T17:55:20+00:00"
},
{
"name": "smarty-gettext/smarty-gettext",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/smarty-gettext/smarty-gettext.git",
"reference": "00fe2fcbc41e24e0245cd9d73f96bc7b0337972d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-gettext/smarty-gettext/zipball/00fe2fcbc41e24e0245cd9d73f96bc7b0337972d",
"reference": "00fe2fcbc41e24e0245cd9d73f96bc7b0337972d",
"shasum": ""
},
"require": {
"ext-gettext": "*",
"ext-pcre": "*",
"php": "~5.3|~7.0"
},
"require-dev": {
"azatoth/php-pgettext": "~1.0",
"smarty/smarty": "3.1.*"
},
"suggest": {
"azatoth/php-pgettext": "Support msgctxt for {t} via context parameter"
},
"bin": [
"tsmarty2c.php"
],
"type": "library",
"autoload": {
"files": [
"block.t.php",
"function.locale.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1+"
],
"authors": [
{
"name": "Sagi Bashari",
"email": "sagi@boom.org.il"
},
{
"name": "Elan Ruusamäe",
"email": "glen@delfi.ee"
}
],
"description": "Gettext plugin enabling internationalization in Smarty Package files",
"homepage": "https://github.com/smarty-gettext/smarty-gettext",
"time": "2017-05-12T12:14:46+00:00"
},
{
"name": "smarty/smarty",
"version": "v3.1.31",
@ -717,16 +845,16 @@
},
{
"name": "symfony/process",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0"
"reference": "36774717bbd1631be2d0a45acf48aecd5836c867"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
"url": "https://api.github.com/repos/symfony/process/zipball/36774717bbd1631be2d0a45acf48aecd5836c867",
"reference": "36774717bbd1631be2d0a45acf48aecd5836c867",
"shasum": ""
},
"require": {
@ -762,20 +890,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2017-04-12T14:13:17+00:00"
"time": "2017-05-08T01:51:21+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6"
"reference": "4cdb9fec28fba88203a71f6d095018867f7a2065"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6",
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4cdb9fec28fba88203a71f6d095018867f7a2065",
"reference": "4cdb9fec28fba88203a71f6d095018867f7a2065",
"shasum": ""
},
"require": {
@ -817,7 +945,48 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2017-05-01T14:55:58+00:00"
"time": "2017-05-25T23:42:36+00:00"
},
{
"name": "zonuexe/http-accept-language",
"version": "0.4.1",
"source": {
"type": "git",
"url": "https://github.com/BaguettePHP/http-accept-language.git",
"reference": "f71422b1200737aa9d7c7fa83f07cbe4616198d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/BaguettePHP/http-accept-language/zipball/f71422b1200737aa9d7c7fa83f07cbe4616198d5",
"reference": "f71422b1200737aa9d7c7fa83f07cbe4616198d5",
"shasum": ""
},
"require": {
"ext-intl": "*"
},
"require-dev": {
"phploc/phploc": "*",
"phpunit/phpunit": "4.1.*",
"theseer/phpdox": "0.6.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Teto\\HTTP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "USAMI Kenta",
"email": "tadsan@zonu.me"
}
],
"description": "HTTP Accept-Language Header parser",
"time": "2014-10-19T09:22:18+00:00"
}
],
"packages-dev": [
@ -2238,16 +2407,16 @@
},
{
"name": "symfony/var-dumper",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8"
"reference": "eda0d0891a0b60a25015f7b85ea8040b51d72e1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/eda0d0891a0b60a25015f7b85ea8040b51d72e1e",
"reference": "eda0d0891a0b60a25015f7b85ea8040b51d72e1e",
"shasum": ""
},
"require": {
@ -2302,7 +2471,7 @@
"debug",
"dump"
],
"time": "2017-05-01T14:55:58+00:00"
"time": "2017-05-15T12:02:31+00:00"
},
{
"name": "webmozart/assert",

View File

@ -6,6 +6,7 @@
namespace Alltube\Controller;
use Alltube\Config;
use Alltube\Locale;
use Alltube\PasswordException;
use Alltube\VideoDownload;
use Psr\Container\ContainerInterface;
@ -61,6 +62,13 @@ class FrontController
*/
private $defaultFormat = 'best[protocol^=http]';
/**
* LocaleManager instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* FrontController constructor.
*
@ -78,6 +86,7 @@ class FrontController
$this->download = new VideoDownload();
$this->container = $container;
$this->view = $this->container->get('view');
$this->localeManager = $this->container->get('locale');
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController');
@ -101,17 +110,35 @@ class FrontController
$response,
'index.tpl',
[
'config' => $this->config,
'class' => 'index',
'description' => 'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.',
'domain' => $uri->getScheme().'://'.$uri->getAuthority(),
'canonical' => $this->getCanonicalUrl($request),
'config' => $this->config,
'class' => 'index',
'description' => 'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.',
'domain' => $uri->getScheme().'://'.$uri->getAuthority(),
'canonical' => $this->getCanonicalUrl($request),
'supportedLocales' => $this->localeManager->getSupportedLocales(),
'locale' => $this->localeManager->getLocale(),
]
);
return $response;
}
/**
* Switch locale.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param array $data Query parameters
*
* @return Response
*/
public function locale(Request $request, Response $response, array $data)
{
$this->localeManager->setLocale(new Locale($data['locale']));
return $response->withRedirect($this->container->get('router')->pathFor('index'));
}
/**
* Display a list of extractors.
*
@ -132,6 +159,7 @@ class FrontController
'description' => 'List of all supported websites from which Alltube Download '.
'can extract video or audio files',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -156,6 +184,7 @@ class FrontController
'title' => 'Password prompt',
'description' => 'You need a password in order to download this video with Alltube Download',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -246,6 +275,7 @@ class FrontController
'protocol' => $protocol,
'config' => $this->config,
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -297,6 +327,7 @@ class FrontController
'class' => 'video',
'title' => 'Error',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);

View File

@ -1,10 +1,10 @@
body {
text-align:center;
background-color: #EBEBEB;
background-image:url('../img/fond.jpg');
font-family: 'Open Sans', sans-serif;
font-weight:400;
text-align:center;
}
@ -12,90 +12,90 @@ body {
/************************HEADER******************************/
header {
position:absolute;
top:0;
text-align:right;
width:100%;
padding:0;
}
position:absolute;
text-align:right;
top:0;
width:100%;
}
.social
{padding-right:21px;}
.social {
padding-right:21px;
}
header a
{
overflow:hidden;
height:38px;
width:38px;
position:relative;
float:right;
margin-top:13px;
margin-left:13px;
margin-right:0;
background-position:0 0;
background-repeat:no-repeat;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
header .social a {
background-position:0 0;
background-repeat:no-repeat;
float:right;
height:38px;
margin-left:13px;
margin-right:0;
margin-top:13px;
overflow:hidden;
position:relative;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
width:38px;
}
header a:focus,
header a:hover
{
outline:none;
header a:hover {
background-position:0 100%;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
.share
{background-image:url('../img/share.png');}
.sharemask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/sharemask.png');
background-position:top left;
background-repeat:no-repeat;
.share {
background-image:url('../img/share.png');
}
.facebook
{background-image:url('../img/facebook.png');}
.sharemask {
background-image:url('../img/sharemask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
.facebookmask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/facebookmask.png');
background-position:top left;
background-repeat:no-repeat;
}
.twitter
{background-image:url('../img/twitter.png');}
.facebook {
background-image:url('../img/facebook.png');
}
.twittermask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/twittermask.png');
background-position:top left;
background-repeat:no-repeat;
.facebookmask {
background-image:url('../img/facebookmask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
}
.twitter {
background-image:url('../img/twitter.png');
}
.twittermask {
background-image:url('../img/twittermask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
}
@ -104,16 +104,16 @@ background-repeat:no-repeat;
footer {
position:fixed;
bottom:0;
text-align:center;
width:100%;
background-image:url('../img/fondfooter.png');
background-repeat:repeat-x;
background-position:top left;
padding-top:20px;
background-repeat:repeat-x;
bottom:0;
color:#adadad;
font-size:12px;
padding-top:20px;
position:fixed;
text-align:center;
width:100%;
z-index:11;
}
@ -123,20 +123,19 @@ footer {
footer a{
color:#adadad;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
text-decoration:none;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
footer a:focus,
footer a:hover
{
footer a:hover {
color:#f2084a;
outline:none;
color:#f2084a;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
@ -150,46 +149,42 @@ color:#f2084a;
padding-bottom:55px;
}
.labelurl
{
position:relative;
color:#3f3f3f;
font-size:19px;
.labelurl {
color:#3f3f3f;
font-size:19px;
position:relative;
}
.champs
{
position:relative;
margin-bottom:70px;
margin-top:8px;
.champs {
margin-bottom:70px;
margin-top:8px;
position:relative;
}
.downloadBtn {
position:relative;
background-color:#3A3A3A;
border: 3px solid #a5a5a5;
color:#dedede;
border-radius:10px;
padding: 12px 14px;
color:#dedede;
cursor:pointer;
display:inline-block;
font-size:24px;
font-weight:800;
cursor:pointer;
padding: 12px 14px;
position:relative;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
text-decoration:none;
display:inline-block;
}
.downloadBtn:focus,
.downloadBtn:hover
{
outline:none;
.downloadBtn:hover {
background-color:#f2084a;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
.downloadBtn::-moz-focus-inner {
@ -197,22 +192,22 @@ margin-top:8px;
}
.URLinput{
position:relative;
background-color:#fff;
border: 3px solid #a5a5a5;
color:#3F3F3F;
border-radius:10px;
padding: 12px 12px 12px 12px;
min-width:426px;
color:#3F3F3F;
font-size:24px;
font-weight:800;
margin-right:8px;
min-width:426px;
padding: 12px 12px 12px 12px;
position:relative;
}
.URLinput:focus {
outline: none;
border-color:#3A3A3A;
outline: none;
}
.URLinput:-webkit-input-placeholder{
@ -223,176 +218,167 @@ margin-top:8px;
}
.combatiblelink {
position:relative;
color:#a5a5a5;
font-size:13px;
z-index:10;
text-decoration:none;
background-image:url('../img/compatiblerouage.png');
background-position:0 100%;
background-repeat:no-repeat;
padding-left:41px;
padding-top:10px;
padding-bottom:10px;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
background-image:url('../img/compatiblerouage.png');
background-position:0 100%;
background-repeat:no-repeat;
color:#a5a5a5;
font-size:13px;
padding-bottom:10px;
padding-left:41px;
padding-top:10px;
position:relative;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
z-index:10;
}
.combatiblelink:focus,
.combatiblelink:hover
{
.combatiblelink:hover {
background-position:0 0;
color:#f2084a;
outline:none;
background-position:0 0;
color:#f2084a;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
#bookmarklet{
padding:15px;
}
.bookmarklet{
position:relative;
font-size:13px;
color:gray;
z-index:10;
text-decoration:none;
padding-left:30px;
padding-right:30px;
padding-top:10px;
padding-bottom:10px;
border: 2px dotted;
}
.mp3
{
.bookmarklet {
border: 2px dotted;
color:gray;
font-size:13px;
padding:10px 30px;
position:relative;
text-decoration:none;
z-index:10;
}
.mp3 {
background-color:#cecece;
color:#696969;
border-radius:6px;
width:622px;
color:#696969;
font-size:14px;
font-weight:300;
height:26px;
margin-top:12px;
position:relative;
text-align:left;
font-weight:300;
width:622px;
}
.mp3 p
{
padding:3px;
.mp3 p {
padding:3px;
}
/*
Demo CSS code
*/
.audio:not(:checked),
.audio:checked {
left: -9999px;
position: absolute;
}
.audio:not(:checked) + label,
.audio:checked + label {
cursor: pointer;
line-height:22px;
padding-left: 82px;
position: relative;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before,
.audio:not(:checked) + label:after,
.audio:checked + label:after {
content: '';
position: absolute;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before {
background: #ffffff;
border-radius: 6px;
height: 20px;
left:0;
top: -1px;
-webkit-transition: background-color .2s;
-moz-transition: background-color .2s;
-ms-transition: background-color .2s;
-o-transition: background-color .2s;
transition: background-color .2s;
width: 45px;
}
.audio:not(:checked) + label:after,
.audio:checked + label:after {
background: #3a3a3a;
border-radius: 6px;
height: 16px;
left: 2px;
top: 1px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
width: 16px;
}
.audio:not(:checked),
.audio:checked {
position: absolute;
left: -9999px;
}
.audio:not(:checked) + label,
.audio:checked + label {
position: relative;
padding-left: 82px;
cursor: pointer;
line-height:22px;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before,
.audio:not(:checked) + label:after,
.audio:checked + label:after {
content: '';
position: absolute;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before {
left:0; top: -1px;
width: 45px; height: 20px;
background: #ffffff;
border-radius: 6px;
-webkit-transition: background-color .2s;
-moz-transition: background-color .2s;
-ms-transition: background-color .2s;
-o-transition: background-color .2s;
transition: background-color .2s;
}
.audio:not(:checked) + label:after,
.audio:checked + label:after {
width: 16px; height: 16px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
border-radius: 6px;
background: #3a3a3a;
top: 1px; left: 2px;
}
.audio:focus + label {
color:black;
}
.audio:focus + label {
color:black;
}
/* on checked */
.audio:checked + label:before {
background:#f2084a;
}
.audio:checked + label:after {
background: #fff;
left: 27px;
top: 1px;
}
/* on checked */
.audio:checked + label:before {
background:#f2084a;
}
.audio:checked + label:after {
background: #fff;
top: 1px; left: 27px;
}
.audio:checked + label .ui,
.audio:not(:checked) + label .ui:before,
.audio:checked + label .ui:after {
position: absolute;
left: 3px;
width: 45px;
border-radius: 15px;
font-size: 11px;
font-weight: bold;
line-height: 17px;
height:20px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
.audio:not(:checked) + label .ui:before {
content: "no";
left: 0;
padding-left:23px;
padding-top:2px;
background-image:url('../img/mp3hover.png');
background-repeat:no-repeat;
background-position:right top;
width:56px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
.audio:checked + label .ui:after {
content: "yes";
color: #fff;
background-image:url('../img/mp3.png');
background-repeat:no-repeat;
background-position:right top;
width:73px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
.audio:checked + label .ui,
.audio:not(:checked) + label .ui:before,
.audio:checked + label .ui:after {
border-radius: 15px;
font-size: 11px;
font-weight: bold;
height:20px;
left: 3px;
line-height: 17px;
position: absolute;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
width: 45px;
}
.audio:not(:checked) + label .ui:before {
background-image:url('../img/mp3hover.png');
background-position:right top;
background-repeat:no-repeat;
content: "no";
left: 0;
padding-left:23px;
padding-top:2px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
width:56px;
}
.audio:checked + label .ui:after {
background-image:url('../img/mp3.png');
background-position:right top;
background-repeat:no-repeat;
color: #fff;
content: "yes";
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
width:73px;
}
@ -423,8 +409,8 @@ padding:3px;
}
.playlist-entry .downloadBtn {
font-size: 16px;
border-width: 2px;
font-size: 16px;
}
@ -432,98 +418,93 @@ padding:3px;
/*************************CONTENT COMPATIBLES****************************/
.logobis
{
width:447px;
height:107px;
position:relative;
margin:0 auto 10px auto;
.logobis {
height:107px;
margin:0 auto 10px auto;
position:relative;
width:447px;
}
.logocompatible
{
width:447px;
height:107px;
background-image:url('../img/logocompatible.png');
background-repeat:repeat-y;
background-position:0 0;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
display:block;
.logocompatible {
background-image:url('../img/logocompatible.png');
background-position:0 0;
background-repeat:repeat-y;
display:block;
height:107px;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
width:447px;
}
.logocompatible:focus,
.logocompatible:hover {
outline:none;
background-position:0 100%;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;}
.logocompatiblemask
{
z-index:10;
position:absolute;
top:0;
left:0;
width:447px;
height:107px;
background-image:url('../img/logocompatiblemask.png');
background-position:0 100%;
background-repeat:no-repeat;
}
.titre
{
font-family: 'Open Sans', sans-serif;
font-weight:300;
color:#383838;
font-size:48px;
}
.tripleliste
{
margin-top:80px;
width:800px;
position:relative;
margin-left:auto;
margin-right:auto;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
.tripleliste ul
{
margin-bottom:1em;
width:600px;
margin-left:120px;}
.logocompatiblemask {
background-image:url('../img/logocompatiblemask.png');
background-position:0 100%;
background-repeat:no-repeat;
height:107px;
left:0;
position:absolute;
top:0;
width:447px;
z-index:10;
}
.tripleliste ul li
{text-align:left;
List-Style-Type:none;
color:#383838;
font-size:16px;
.titre {
color:#383838;
font-family: 'Open Sans', sans-serif;
font-size:48px;
font-weight:300;
}
width:200px;
float:left;
position:relative;
.tripleliste {
margin-left:auto;
margin-right:auto;
margin-top:80px;
position:relative;
width:800px;
}
.tripleliste ul {
margin-bottom:1em;
margin-left:120px;
width:600px;
}
.tripleliste ul li {
color:#383838;
float:left;
font-size:16px;
list-style-type:none;
position:relative;
text-align:left;
width:200px;
}
html,
body {
margin:0;
height:100%;
margin:0;
}
.wrapper {
height:100%;
display:table;
margin:auto;
padding-bottom:110px;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display:table;
height:100%;
margin:auto;
padding-bottom:110px;
}
.main {
display:table-cell;
@ -595,6 +576,66 @@ h1 {
font-family:monospace;
}
.locales {
float: left;
font-size: 12px;
padding-left: 1em;
padding-top: 1em;
text-align: left;
}
.locales a,
.locales a:visited {
color: #696969;
text-decoration: none;
}
.supportedLocales {
background-color: #fff;
list-style-type: none;
margin: 0;
opacity: 0;
padding-left: 0;
transition: visibility 0.5s;
visibility: hidden;
}
.supportedLocales li {
border-bottom: thin solid #E1E1E1;
}
.supportedLocales li:last-child {
border-bottom: none;
}
.supportedLocales li a {
display: block;
padding: 1em;
padding-right: 2em;
}
.supportedLocales li:hover {
background-color: #cecece;
}
.localesBtn {
border: none;
cursor: pointer;
display: inline-block;
font-size: 12px;
padding: 1em;
}
.localesBtn:focus {
background-color: #fff;
pointer-events: none;
}
.localesBtn:focus + .supportedLocales {
opacity: 1;
visibility: visible;
}
@media (max-width: 640px) {
.formats,
.thumb {
@ -619,9 +660,9 @@ h1 {
.champs,
.URLinput,
.mp3 {
width:90%;
margin:auto;
height:auto;
margin:auto;
width:90%;
}
.logo {
@ -629,8 +670,8 @@ h1 {
}
.logocompatible img {
width:100%;
height: auto;
width:100%;
}
.downloadBtn {
@ -646,9 +687,9 @@ h1 {
.tripleliste ul,
.tripleliste {
width:auto;
margin-left:auto;
margin-top:auto;
width:auto;
}
.logocompatiblemask {
@ -656,9 +697,9 @@ h1 {
}
.logocompatible {
height:auto;
background-image:none;
background-color:#4F4F4F;
background-image:none;
height:auto;
}
.logocompatiblemask,

View File

@ -0,0 +1,134 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Pierre Rudloff <contact@rudloff.pro>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.6.10\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: templates/error.tpl:5
msgid "An error occured"
msgstr "Une erreur est survenue"
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr "Veuillez vérifier l'URL de votre vidéo"
#: templates/playlist.tpl:5
msgid "Videos extracted from"
msgstr "Vidéos extraites depuis"
#: templates/playlist.tpl:7
msgid ":"
msgstr " :"
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:85
#: templates/video.tpl:88 templates/index.tpl:18
msgid "Download"
msgstr "Télécharger"
#: templates/playlist.tpl:27
msgid "More options"
msgstr "Plus d'options"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "Cette vidéo est protégée"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "L'accès à cette vidéo nécessite un mot de passe."
#: templates/password.tpl:8
msgid "Video password"
msgstr "Mot de passe de la vidéo"
#: templates/extractors.tpl:4
msgid "Supported websites"
msgstr "Sites web supportés"
#: templates/video.tpl:6
msgid "You are going to download"
msgstr "Vous allez télécharger"
#: templates/video.tpl:26
msgid "Available formats:"
msgstr "Formats disponibles :"
#: templates/video.tpl:31
msgid "Generic formats"
msgstr "Formats génériques"
#: templates/video.tpl:34
msgid "Best"
msgstr "Meilleure qualité"
#: templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr "Combiner la meilleure vidéo avec le meilleur audio"
#: templates/video.tpl:43
msgid "Worst"
msgstr "Pire qualité"
#: templates/video.tpl:46
msgid "Detailed formats"
msgstr "Formats détaillés"
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr "Développé par"
#: templates/inc/footer.tpl:6
msgid "Design by"
msgstr "Designé par"
#: templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr "AllTube Download sur Facebook"
#: templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr "Suivez-nous sur Facebook"
#: templates/inc/footer.tpl:14
msgid "Get the code"
msgstr "Obtenir le code"
#: templates/inc/footer.tpl:16
msgid "Based on"
msgstr "Basé sur"
#: templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr "Partager sur Twitter"
#: templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr "Partager sur Facebook"
#: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr "Copiez ici l'URL de votre vidéo (Youtube, Dailymotion, etc.)"
#: templates/index.tpl:23
msgid "Audio only (MP3)"
msgstr "Audio uniquement (MP3)"
#: templates/index.tpl:28
msgid "See all supported websites"
msgstr "Voir tous les sites supportés"
#: templates/index.tpl:30
msgid "Drag this to your bookmarks bar:"
msgstr "Glissez ce lien dans votre barre de favoris :"
#: templates/index.tpl:31
msgid "Bookmarklet"
msgstr "Bookmarklet"

123
i18n/template.pot Normal file
View File

@ -0,0 +1,123 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
#: templates/error.tpl:5
msgid "An error occured"
msgstr ""
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr ""
#: templates/playlist.tpl:5
msgid "Videos extracted from"
msgstr ""
#: templates/playlist.tpl:7
msgid ":"
msgstr ""
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:85
#: templates/video.tpl:88 templates/index.tpl:18
msgid "Download"
msgstr ""
#: templates/playlist.tpl:27
msgid "More options"
msgstr ""
#: templates/password.tpl:5
msgid "This video is protected"
msgstr ""
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr ""
#: templates/password.tpl:8
msgid "Video password"
msgstr ""
#: templates/extractors.tpl:4
msgid "Supported websites"
msgstr ""
#: templates/video.tpl:6
msgid "You are going to download"
msgstr ""
#: templates/video.tpl:26
msgid "Available formats:"
msgstr ""
#: templates/video.tpl:31
msgid "Generic formats"
msgstr ""
#: templates/video.tpl:34
msgid "Best"
msgstr ""
#: templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr ""
#: templates/video.tpl:43
msgid "Worst"
msgstr ""
#: templates/video.tpl:46
msgid "Detailed formats"
msgstr ""
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr ""
#: templates/inc/footer.tpl:6
msgid "Design by"
msgstr ""
#: templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr ""
#: templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr ""
#: templates/inc/footer.tpl:14
msgid "Get the code"
msgstr ""
#: templates/inc/footer.tpl:16
msgid "Based on"
msgstr ""
#: templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr ""
#: templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr ""
#: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr ""
#: templates/index.tpl:23
msgid "Audio only (MP3)"
msgstr ""
#: templates/index.tpl:28
msgid "See all supported websites"
msgstr ""
#: templates/index.tpl:30
msgid "Drag this to your bookmarks bar:"
msgstr ""
#: templates/index.tpl:31
msgid "Bookmarklet"
msgstr ""

View File

@ -0,0 +1,135 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Shiqiang Yu <tony19955569@gmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: ..\..\..\templates/error.tpl:5
msgid "An error occured"
msgstr "出错了"
#: ..\..\..\templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr "请检查您的视频的 URL。"
#: ..\..\..\templates/extractors.tpl:4
msgid "Supported websites"
msgstr "支持的网站"
#: ..\..\..\templates/inc/footer.tpl:4
msgid "Code by"
msgstr "代码来自"
#: ..\..\..\templates/inc/footer.tpl:6
msgid "Design by"
msgstr "设计来自"
#: ..\..\..\templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr "去Alltube Download的Facebook页面"
#: ..\..\..\templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr "在Facebook关注我们"
#: ..\..\..\templates/inc/footer.tpl:14
msgid "Get the code"
msgstr "获取代码"
#: ..\..\..\templates/inc/footer.tpl:16
msgid "Based on"
msgstr "基于"
#: ..\..\..\templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr "分享到 Twitter"
#: ..\..\..\templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr "分享到 Facebook"
#: ..\..\..\templates/index.tpl:9
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr "在这里复制您的视频 Youtube、 Dailymotion 等) 的 URL"
#: ..\..\..\templates/index.tpl:19 ..\..\..\templates/password.tpl:10
#: ..\..\..\templates/playlist.tpl:23 ..\..\..\templates/video.tpl:85
#: ..\..\..\templates/video.tpl:90
msgid "Download"
msgstr "下载"
#: ..\..\..\templates/index.tpl:24
msgid "Audio only (MP3)"
msgstr "仅限音频mp3"
#: ..\..\..\templates/index.tpl:29
msgid "See all supported websites"
msgstr "请参阅支持的所有网站"
#: ..\..\..\templates/index.tpl:31
msgid "Drag this to your bookmarks bar:"
msgstr "把这个拖到你的书签:"
#: ..\..\..\templates/index.tpl:32
msgid "Bookmarklet"
msgstr "书签工具"
#: ..\..\..\templates/password.tpl:5
msgid "This video is protected"
msgstr "这个视频受保护"
#: ..\..\..\templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "你需要密码才能下载这个视频。"
#: ..\..\..\templates/password.tpl:8
msgid "Video password"
msgstr "视频密码"
#: ..\..\..\templates/playlist.tpl:5
msgid "Videos extracted from the"
msgstr "视频从"
#: ..\..\..\templates/playlist.tpl:7
msgid "playlist:"
msgstr "播放列表提取:"
#: ..\..\..\templates/playlist.tpl:24
msgid "More options"
msgstr "更多选项"
#: ..\..\..\templates/video.tpl:6
msgid "You are going to download"
msgstr "你即将下载"
#: ..\..\..\templates/video.tpl:24
msgid "Available formats:"
msgstr "可用的格式︰"
#: ..\..\..\templates/video.tpl:31
msgid "Generic formats"
msgstr "通用格式"
#: ..\..\..\templates/video.tpl:34
msgid "Best"
msgstr "最佳"
#: ..\..\..\templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频与最佳音频"
#: ..\..\..\templates/video.tpl:43
msgid "Worst"
msgstr "最差"
#: ..\..\..\templates/video.tpl:46
msgid "Detailed formats"
msgstr "详细格式"

View File

@ -3,6 +3,8 @@
require_once __DIR__.'/vendor/autoload.php';
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use Alltube\PlaylistArchiveStream;
use Alltube\UglyRouter;
use Alltube\ViewFactory;
@ -22,6 +24,8 @@ if ($config->uglyUrls) {
$container['router'] = new UglyRouter();
}
$container['view'] = ViewFactory::create($container);
$container['locale'] = new LocaleManager($_COOKIE);
$app->add(new LocaleMiddleware($container));
$controller = new FrontController($container, null, $_COOKIE);
@ -43,4 +47,8 @@ $app->get(
'/redirect',
[$controller, 'redirect']
)->setName('redirect');
$app->get(
'/locale/{locale}',
[$controller, 'locale']
)->setName('locale');
$app->run();

52
package-lock.json generated
View File

@ -227,6 +227,11 @@
"version": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.3.tgz",
"integrity": "sha1-B8/omA7bINRV3cI6rc8eBMblCc4="
},
"clone": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
"integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -303,6 +308,11 @@
"dev": true,
"optional": true
},
"csslint": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/csslint/-/csslint-1.0.5.tgz",
"integrity": "sha1-Gcw+2jIhYP0/cjKvHLKjYOiYouk="
},
"currently-unhandled": {
"version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o="
@ -579,6 +589,23 @@
}
}
},
"grunt-contrib-csslint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-csslint/-/grunt-contrib-csslint-2.0.0.tgz",
"integrity": "sha1-MSnZTf5Qc1fyMzfSSunpqkudV98=",
"dependencies": {
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
}
}
},
"grunt-contrib-cssmin": {
"version": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.0.tgz",
"integrity": "sha1-ct70BijujiE62adoBNEx22ZgSkE="
@ -676,6 +703,11 @@
"integrity": "sha1-DnW+5rXC5l/aRQdWcqBs6yzs2Gk=",
"dev": true
},
"grunt-potomo": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/grunt-potomo/-/grunt-potomo-3.5.0.tgz",
"integrity": "sha1-WtjG9+djrVtRg55cbTI1gGLHN5U="
},
"gzip-size": {
"version": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
"integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA="
@ -770,6 +802,11 @@
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
"dev": true
},
"interpret": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
"integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A="
},
"is-arrayish": {
"version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
@ -1139,6 +1176,11 @@
"version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck="
},
"parserlib": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/parserlib/-/parserlib-1.1.1.tgz",
"integrity": "sha1-pkz6ckBiQ0/fw1HJpOwtkrlMBvQ="
},
"parseurl": {
"version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
"integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=",
@ -1236,6 +1278,11 @@
"version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
"integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g="
},
"rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q="
},
"redent": {
"version": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94="
@ -1288,6 +1335,11 @@
"dev": true,
"optional": true
},
"shelljs": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
"integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E="
},
"signal-exit": {
"version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="

View File

@ -7,8 +7,10 @@
"dependencies": {
"bower": "~1.8.0",
"grunt": "~1.0.1",
"grunt-contrib-csslint": "~2.0.0",
"grunt-contrib-cssmin": "~2.2.0",
"grunt-contrib-uglify": "~3.0.0"
"grunt-contrib-uglify": "~3.0.0",
"grunt-potomo": "~3.5.0"
},
"devDependencies": {
"grunt-contrib-compress": "~1.4.1",

View File

@ -2,8 +2,8 @@
<div class="wrapper">
<div class="main error">
{include file="inc/logo.tpl"}
<h2>An error occured</h2>
Please check the URL of your video.
<h2>{t}An error occured{/t}</h2>
{t}Please check the URL of your video.{/t}
<p><i>
{foreach $errors as $error}
{$error|escape}

View File

@ -1,7 +1,7 @@
{include file='inc/head.tpl'}
{include file='inc/header.tpl'}
{include file='inc/logo.tpl'}
<h2 class="titre">Supported websites</h2>
<h2 class="titre">{t}Supported websites{/t}</h2>
<div class="tripleliste">
<ul>
{foreach $extractors as $extractor}

View File

@ -1,19 +1,19 @@
</div>
<footer>
<div class="footer_wrapper">
Code by <a rel="author" target="blank"
{t}Code by{/t} <a rel="author" target="blank"
href="http://rudloff.pro/">Pierre Rudloff</a>
&middot; Design by
&middot; {t}Design by{/t}
<a rel="author" target="blank"
href="http://olivierhaquette.fr">Olivier Haquette</a>
&middot;
<a target="_blank"
href="https://www.facebook.com/pages/AllTube-Download/571380966249415"
title="AllTube Download on Facebook">Like us on Facebook</a>
title="{t}AllTube Download on Facebook{/t}">{t}Like us on Facebook{/t}</a>
&middot;
<a href="https://github.com/Rudloff/alltube">Get the code</a>
<a href="https://github.com/Rudloff/alltube">{t}Get the code{/t}</a>
&middot;
Based on <a href="http://rg3.github.io/youtube-dl/">youtube-dl</a>
{t}Based on{/t} <a href="http://rg3.github.io/youtube-dl/">youtube-dl</a>
</div>
</footer>
<script src="{base_url}/dist/main.js"></script>

View File

@ -1,5 +1,6 @@
{locale path="../i18n" domain="Alltube"}
<!Doctype HTML>
<html lang="en">
<html {if isset($locale)}lang="{$locale->getBcp47()}"{/if}>
<head>
<meta charset="UTF-8" />
<meta name=viewport content="width=device-width, initial-scale=1">

View File

@ -1,8 +1,26 @@
<header>
<div class="social">
<a class="twitter" href="http://twitter.com/home?status={base_url|urlencode}" target="_blank">
Share on Twitter<div class="twittermask"></div></a>
<a class="facebook" href="https://www.facebook.com/sharer/sharer.php?u={base_url|urlencode}" target="_blank">Share on Facebook<div class="facebookmask"></div></a>
{t}Share on Twitter{/t}<div class="twittermask"></div></a>
<a class="facebook" href="https://www.facebook.com/sharer/sharer.php?u={base_url|urlencode}" target="_blank">{t}Share on Facebook{/t}<div class="facebookmask"></div></a>
</div>
{if isset($supportedLocales)}
<div class="locales">
<button class="localesBtn" title="{t}Switch language{/t}">
{if isset($locale)}
{$locale->getCountry()->getEmoji()}
{else}
Set language
{/if}
</button>
<ul class="supportedLocales">
{foreach $supportedLocales as $supportedLocale}
{if $supportedLocale != $locale}
<li><a hreflang="{$supportedLocale->getBcp47()}" lang="{$supportedLocale->getBcp47()}" href="{path_for name='locale' data=['locale'=>$supportedLocale->getIso15897()]}">{$supportedLocale->getCountry()->getEmoji()} {$supportedLocale->getFullName()}</a></li>
{/if}
{/foreach}
</ul>
{/if}
</div>
</header>
<div class="wrapper">

View File

@ -5,7 +5,7 @@
alt="AllTube Download" width="328" height="284"></div>
<form action="{path_for name="video"}">
<label class="labelurl" for="url">
Copy here the URL of your video (Youtube, Dailymotion, etc.)
{t}Copy here the URL of your video (Youtube, Dailymotion, etc.){/t}
</label>
<div class="champs">
<span class="URLinput_wrapper">
@ -15,20 +15,20 @@
{if $config->uglyUrls}
<input type="hidden" name="page" value="video" />
{/if}
<input class="downloadBtn" type="submit" value="Download" /><br/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
{if $config->convert}
<div class="mp3">
<p><input type="checkbox" id="audio" class="audio" name="audio">
<label for="audio"><span class="ui"></span>
Audio only (MP3)</label></p>
{t}Audio only (MP3){/t}</label></p>
</div>
{/if}
</div>
</form>
<a class="combatiblelink" href="{path_for name="extractors"}">See all supported websites</a>
<a class="combatiblelink" href="{path_for name="extractors"}">{t}See all supported websites{/t}</a>
<div id="bookmarklet" class="bookmarklet_wrapper">
<p> Drag this to your bookmarks bar: </p>
<a class="bookmarklet" href="javascript:window.location='{$domain}{path_for name='video'}?url='+encodeURIComponent(location.href);">Bookmarklet</a>
<p> {t}Drag this to your bookmarks bar:{/t} </p>
<a class="bookmarklet" href="javascript:window.location='{$domain}{path_for name='video'}?url='+encodeURIComponent(location.href);">{t}Bookmarklet{/t}</a>
</div>
</div>

View File

@ -2,12 +2,12 @@
<div class="wrapper">
<div class="main">
{include file="inc/logo.tpl"}
<h2>This video is protected</h2>
<p>You need a password in order to download this video.</p>
<h2>{t}This video is protected{/t}</h2>
<p>{t}You need a password in order to download this video.{/t}</p>
<form action="" method="POST">
<input class="URLinput" type="password" name="password" title="Video password" />
<input class="URLinput" type="password" name="password" title="{t}Video password{/t}" />
<br/><br/>
<input class="downloadBtn" type="submit" value="Download" />
<input class="downloadBtn" type="submit" value="{t}Download{/t}" />
</form>
</div>
{include file='inc/footer.tpl'}

View File

@ -2,9 +2,9 @@
<div class="wrapper">
<div class="main">
{include file="inc/logo.tpl"}
<p>Videos extracted from the {if isset($video->title)}<i>
<p>{t}Videos extracted from{/t} {if isset($video->title)}<i>
<a href="{$video->webpage_url}">
{$video->title}</a></i>{/if} playlist:
{$video->title}</a></i>{/if}{t}:{/t}
</p>
{if $config->stream}
<a href="{path_for name="redirect"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a>
@ -23,8 +23,8 @@
{$video->title}
{/if}
</a></h3>
<a target="_blank" class="downloadBtn" href="{path_for name="redirect"}?url={$video->url}">Download</a>
<a target="_blank" href="{path_for name="video"}?url={$video->url}">More options</a>
<a target="_blank" class="downloadBtn" href="{path_for name="redirect"}?url={$video->url}">{t}Download{/t}</a>
<a target="_blank" href="{path_for name="video"}?url={$video->url}">{t}More options{/t}</a>
</div>
{/foreach}

View File

@ -3,7 +3,7 @@
<div itemscope itemtype="http://schema.org/VideoObject">
<div class="main">
{include file="inc/logo.tpl"}
<p id="download_intro">You are going to download<i itemprop="name">
<p id="download_intro">{t}You are going to download{/t}<i itemprop="name">
<a itemprop="url" id="video_link"
data-ext="{$video->ext}"
data-video="{$video->url|escape}"
@ -23,27 +23,27 @@
<form action="{path_for name="redirect"}">
<input type="hidden" name="url" value="{$video->webpage_url}" />
{if isset($video->formats)}
<h3><label for="format">Available formats:</label></h3>
<h3><label for="format">{t}Available formats:{/t}</label></h3>
{if $config->uglyUrls}
<input type="hidden" name="page" value="redirect" />
{/if}
<select name="format" id="format" class="formats monospace">
<optgroup label="Generic formats">
<optgroup label="{t}Generic formats{/t}">
<option value="best{$protocol}">
{strip}
Best ({$video->ext})
{t}Best{/t} ({$video->ext})
{/strip}
</option>
{if $config->remux}
<option value="bestvideo+bestaudio">
Remux best video with best audio
{t}Remux best video with best audio{/t}
</option>
{/if}
<option value="worst{$protocol}">
Worst
{t}Worst{/t}
</option>
</optgroup>
<optgroup label="Detailed formats" class="monospace">
<optgroup label="{t}Detailed formats{/t}" class="monospace">
{foreach $video->formats as $format}
{if $config->stream || $format->protocol|in_array:array('http', 'https')}
{strip}
@ -82,10 +82,10 @@
{/foreach}
</optgroup>
</select><br/><br/>
<input class="downloadBtn" type="submit" value="Download" /><br/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
</form>
{else}
<input class="downloadBtn" type="submit" value="Download" /><br/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
{/if}
</div>
</div>

View File

@ -7,6 +7,7 @@ namespace Alltube\Test;
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Http\Environment;
@ -55,6 +56,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->request = Request::createFromEnvironment(Environment::mock());
$this->response = new Response();
$this->container['view'] = ViewFactory::create($this->container, $this->request);
$this->container['locale'] = new LocaleManager();
$this->controller = new FrontController($this->container, Config::getInstance('config/config_test.yml'));
$this->container['router']->map(['GET'], '/', [$this->controller, 'index'])
->setName('index');
@ -64,6 +66,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
->setName('extractors');
$this->container['router']->map(['GET'], '/redirect', [$this->controller, 'redirect'])
->setName('redirect');
$this->container['router']->map(['GET'], '/locale', [$this->controller, 'locale'])
->setName('locale');
}
/**
@ -481,4 +485,20 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
new Config(['stream'=>true])
);
}
/**
* Test the locale() function.
*
* @return void
*/
public function testLocale()
{
$this->assertTrue(
$this->controller->locale(
$this->request,
$this->response,
['locale'=> 'fr_FR']
)->isRedirect()
);
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* LocaleManagerTest class.
*/
namespace Alltube\Test;
use Alltube\Locale;
use Alltube\LocaleManager;
/**
* Unit tests for the Config class.
*/
class LocaleManagerTest extends \PHPUnit_Framework_TestCase
{
/**
* LocaleManager class instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->localeManager = new LocaleManager();
}
/**
* Test the getSupportedLocales function.
*
* @return void
*/
public function testConstructorWithCookies()
{
$_SESSION['Alltube\LocaleManager']['locale'] = 'foo_BAR';
$localeManager = new LocaleManager([]);
$this->assertEquals('foo_BAR', (string) $localeManager->getLocale());
}
/**
* Test the getSupportedLocales function.
*
* @return void
*/
public function testGetSupportedLocales()
{
foreach ($this->localeManager->getSupportedLocales() as $locale) {
$this->assertInstanceOf(Locale::class, $locale);
}
}
/**
* Test the getLocale function.
*
* @return void
*/
public function testGetLocale()
{
$this->assertNull($this->localeManager->getLocale());
}
/**
* Test the setLocale function.
*
* @return void
*/
public function testSetLocale()
{
$this->localeManager->setLocale(new Locale('foo_BAR'));
$locale = $this->localeManager->getLocale();
$this->assertInstanceOf(Locale::class, $locale);
$this->assertEquals('foo_BAR', (string) $locale);
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* LocaleMiddlewareTest class.
*/
namespace Alltube\Test;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Unit tests for the FrontController class.
*/
class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
{
/**
* LocaleMiddleware instance.
*
* @var LocaleMiddleware
*/
private $middleware;
/**
* Prepare tests.
*/
protected function setUp()
{
$container = new Container();
$container['locale'] = new LocaleManager();
$this->middleware = new LocaleMiddleware($container);
}
/**
* Test the testLocale() function.
*
* @return void
*/
public function testTestLocale()
{
$locale = [
'language'=> 'fr',
'region' => 'FR',
];
$this->assertEquals('fr_FR', $this->middleware->testLocale($locale));
}
/**
* Test the testLocale() function with an unsupported locale.
*
* @return void
*/
public function testLocaleWithWrongLocale()
{
$locale = [
'language'=> 'foo',
'region' => 'BAR',
];
$this->assertNull($this->middleware->testLocale($locale));
$this->assertNull($this->middleware->testLocale([]));
}
/**
* Test the __invoke() function.
*
* @return void
*/
public function testInvoke()
{
$request = Request::createFromEnvironment(Environment::mock());
$this->middleware->__invoke(
$request->withHeader('Accept-Language', 'fr-FR'),
new Response(),
function () {
}
);
}
/**
* Test the __invoke() function withot the Accept-Language header.
*
* @return void
*/
public function testInvokeWithoutHeader()
{
$request = Request::createFromEnvironment(Environment::mock());
$this->middleware->__invoke(
$request->withoutHeader('Accept-Language'),
new Response(),
function () {
}
);
}
/**
* Test that the environment is correctly set up.
*
* @return void
*/
public function testEnv()
{
$this->markTestIncomplete('We need to find a way to reliably test LC_ALL and LANG values');
}
}

79
tests/LocaleTest.php Normal file
View File

@ -0,0 +1,79 @@
<?php
/**
* LocaleTest class.
*/
namespace Alltube\Test;
use Alltube\Locale;
/**
* Unit tests for the Config class.
*/
class LocaleTest extends \PHPUnit_Framework_TestCase
{
/**
* Locale class instance.
*
* @var Locale
*/
private $locale;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->locale = new Locale('fr_FR');
}
/**
* Test the __toString function.
*
* @return void
*/
public function testGetToString()
{
$this->assertEquals('fr_FR', $this->locale->__toString());
}
/**
* Test the getFullName function.
*
* @return void
*/
public function testGetFullName()
{
$this->assertEquals('français (France)', $this->locale->getFullName());
}
/**
* Test the getIso15897 function.
*
* @return void
*/
public function testGetIso15897()
{
$this->assertEquals('fr_FR', $this->locale->getIso15897());
}
/**
* Test the getBcp47 function.
*
* @return void
*/
public function testGetBcp47()
{
$this->assertEquals('fr-FR', $this->locale->getBcp47());
}
/**
* Test the getIso3166 function.
*
* @return void
*/
public function testGetIso3166()
{
$this->assertEquals('fr', $this->locale->getIso3166());
}
}

View File

@ -151,32 +151,32 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'best[protocol^=http]',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'It_s_Not_Me_It_s_You_-_Hearts_Under_Fire-M7IpKCZ47pU',
'mp4',
'googlevideo.com',
],
[
'https://www.youtube.com/watch?v=RJJ6FCAXvKg', 22,
"'Heart Attack' - Demi Lovato ".
'(Sam Tsui & Against The Current)-RJJ6FCAXvKg',
'Heart_Attack_-_Demi_Lovato_'.
'Sam_Tsui_Against_The_Current-RJJ6FCAXvKg',
'mp4',
'googlevideo.com',
],
[
'https://vimeo.com/24195442', 'best[protocol^=http]',
'Carving the Mountains-24195442',
'Carving_the_Mountains-24195442',
'mp4',
'vimeocdn.com',
],
[
'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best',
'Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m',
'Leonard_Cohen_Kaleidoscope_-_BBC_Radio_4-b039d07m',
'flv',
'bbcodspdns.fcod.llnwd.net',
],
[
'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0', 'bestaudio/best',
'GRIP sucht den Sommerkönig-folge-203-0',
'GRIP_sucht_den_Sommerkonig-folge-203-0',
'f4v',
'edgefcs.net',
],
@ -193,7 +193,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'bestvideo+bestaudio',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'It_s_Not_Me_It_s_You_-_Hearts_Under_Fire-M7IpKCZ47pU',
'mp4',
'googlevideo.com',
],
@ -210,7 +210,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://twitter.com/verge/status/813055465324056576/video/1', 'best',
'The Verge - This tiny origami robot can self-fold and complete tasks-813055465324056576',
'The_Verge_-_This_tiny_origami_robot_can_self-fold_and_complete_tasks-813055465324056576',
'mp4',
'video.twimg.com',
],
@ -227,7 +227,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'http://www.canalc2.tv/video/12163', 'rtmp',
'Terrasses du Numérique-12163',
'Terrasses_du_Numerique-12163',
'flv',
'vod-flash.u-strasbg.fr',
],

View File

@ -7,6 +7,8 @@ namespace Alltube\Test;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
use Slim\Views\Smarty;
/**
@ -24,4 +26,16 @@ class ViewFactoryTest extends \PHPUnit_Framework_TestCase
$view = ViewFactory::create(new Container());
$this->assertInstanceOf(Smarty::class, $view);
}
/**
* Test the create() function with a X-Forwarded-Proto header.
*
* @return void
*/
public function testCreateWithXForwardedProto()
{
$request = Request::createFromEnvironment(Environment::mock());
$view = ViewFactory::create(new Container(), $request->withHeader('X-Forwarded-Proto', 'https'));
$this->assertInstanceOf(Smarty::class, $view);
}
}