Implemented domains in translations. Fix #116 and #177

This commit is contained in:
Franck Allimant
2014-04-20 16:31:06 +02:00
parent 9b29d510f2
commit a135a68e6c
23 changed files with 1262 additions and 938 deletions

View File

@@ -12,6 +12,8 @@
namespace Thelia\Command;
use Thelia\Model\Module;
/**
* base class for module commands
*
@@ -36,7 +38,7 @@ abstract class BaseModuleGenerate extends ContainerAwareCommand
'Controller',
'EventListeners',
'I18n',
'AdminIncludes',
Module::ADMIN_INCLUDES_DIRECTORY_NAME,
'templates',
);

View File

@@ -23,11 +23,11 @@ return array(
'Alpha code 3 *' => 'Code Alpha 3 *',
'Amount removed from the cart' => 'Montant déduit du panier',
'Apply exchange rates on price in %sym' => 'Appliquer le taux de change sur le prix en %sym',
'Area' => 'Zone',
'Attribute ID:Attribute AV ID' => 'Déclinaison ID : Valeur de déclinaison ID',
'Auth mode' => 'Mode d\'authentification',
'Available quantity' => 'Quantité disponible',
'Available quantity *' => 'Quantité disponible *',
'Available shipping zones' => 'Zones de livraison disponibles',
'Bad tax list JSON' => 'Mauvais JSON de la liste des taxes',
'Business ID' => 'ID du business',
'Cannot find a default country. Please define one.' => 'Impossible de trouver un pays par défaut. Veuillez en définir un.',
@@ -64,6 +64,7 @@ return array(
'Emergency' => 'Urgence',
'Enable remote SMTP use' => 'Activer l\'utilisation d\'un serveur SMTP distant.',
'Encryption' => 'Chiffrement',
'Error occured while processing order ref. %ref, ID %id: %err' => 'Un erreur est survenue paedant le traitement de la commande ref. %ref, ID %id; %err',
'Errors' => 'Erreurs',
'Failed to update language definition: %ex' => 'Erreur lors de la mise à jour de la définition de la langue : %ex',
'Fax' => 'Fax',
@@ -91,6 +92,8 @@ return array(
'Log level *' => 'Niveau de log *',
'Login' => 'Connexion',
'Login failed. Please check your username and password.' => 'Erreur de login. Veuillez vérifier votre login et votre mot de passe.',
'Loop must implements \'PropelSearchLoopInterface\' to be timestampable' => 'Les classes \'boucle\' doivent implémenter \'PropelSearchLoopInterface\' pour être horodatables',
'Loop must implements \'PropelSearchLoopInterface\' to be versionable' => 'Les classes \'boucle\' doivent implémenter \'PropelSearchLoopInterface\' pour être versonnables',
'Make this address as my primary address' => 'Choisir cette adresse comme adresse par défaut',
'Message subject' => 'Sujet',
'Meta Description' => 'Meta description',
@@ -105,9 +108,13 @@ return array(
'New Password' => 'Nouveau mot de passe.',
'No %obj was created.' => 'aucun %obj n\'a été créé.',
'No %obj was updated.' => 'Aucun %obj mis à jour',
'No module found for code \'%item\'' => 'Aucun module trouvé pour \'%item\' ',
'No, I am a new customer.' => 'Non, je suis un nouveau client.',
'Not found' => 'Non trouvé.',
'Notices' => 'Notices',
'Order address ID not found' => 'ID de l\'adresse de la commande non trouvé',
'Order ref. %ref is now unpaid.' => 'La commande %ref, ID %id est désormais non payée',
'Order ref. %ref, ID %id has been successfully paid.' => 'La commande ref. %ref, ID %id a été correctement payée',
'Page Title' => 'Titre de la page',
'Parent category *' => 'Catégorie parente *',
'Parent folder *' => 'Dossier parent *',
@@ -129,6 +136,8 @@ return array(
'Prevent variable modification or deletion, except for super-admin' => 'Prévenir la suppression ou la modification de variables, excepté pour les super-administrateurs.',
'Price' => 'Prix',
'Price currency *' => 'Devise *',
'Processing cancelation of payment for order ref. %ref' => 'Traitement de l\'annulation du paiement de la commande %ref, ID %id',
'Processing confirmation of order ref. %ref, ID %id' => 'Traitement de la confirmation de la commande %ref, ID %id',
'Prodcut ID *' => 'ID du produit *',
'Product ID' => 'ID produit',
'Product ID *' => 'ID produit *',
@@ -147,6 +156,7 @@ return array(
'Profile ID not found' => 'ID du profil non trouvé',
'Profile `code` already exists' => 'Le `code` du profil existe déjà',
'Purpose *' => 'Objet',
'Quantity' => 'Quantité',
'Rate from € *' => 'Taux à partie de l\'€ *',
'Redirecting ...' => 'Redirection ...',
'Redirecting to %url' => 'Redirection vers %url',
@@ -162,11 +172,13 @@ return array(
'Rotated Text File' => 'Rotation du fichier texte',
'Sale price excluding taxes' => 'Prix de vente Hors Taxes',
'Sale price including taxes' => 'Prix de vente Toutes Taxes Comprises',
'Shipping zone name' => 'Nom de la zone de livraison',
'Show redirections *' => 'Montrer les redirections *',
'Sorry, an error occured: %msg' => 'Désolé, une erreur est survenue : %msg',
'Sorry, you are not allowed to perform this action.' => 'Désolé, vous n\'êtes pas autorisé à réaliser cette action.',
'Sorry, you\'re not allowed to perform this action' => 'Désolé, vous n\'êtes pas autorisé à réaliser cette action.',
'Source IP' => 'IP source',
'Stats on %month/%year' => 'Statisticues pour %month/%year ',
'Store configuration failed.' => 'Erreur de configuration du magasin.',
'Store email address' => 'Adresse mail du magasin',
'Store logs into text file' => 'Conserver les logs dans des fichiers texte',
@@ -205,13 +217,18 @@ return array(
'Title *' => 'Titre *',
'Title ID not found' => 'ID de la civilité non trouvé',
'Type' => 'Type',
'Unavailable' => 'Non disponible.',
'Undefined loop argument "%name"' => 'Argument de boucle invalide: "%name" ',
'Undefined search mode \'%mode\'' => 'Mode de recherche \'%mode\' invalide',
'Undefined translation type: %item' => 'Type de traduction inconnu:%item ',
'Unknown order ID: %id' => 'La commande ID %id est inconnue.',
'Unsupported magic method %name. only getArgname() is supported.' => 'La méthode magique %name n\'est pas supportée. Seule get<argname>() est supporté..',
'Username' => 'Nom d\'utilisateur',
'Username *' => 'Nom d\'utilisateur *',
'Value' => 'Valeur',
'Value *' => 'Valeur *',
'Warnings' => 'Avertissements',
'Weight' => 'Poids',
'Weight *' => 'Poids *',
'Yes, I have a password :' => 'Oui, j\'ai un mot de passe :',
'You are already registered!' => 'Vous êtes déjà enregistré !',
'Your Email Address' => 'Votre adresse mail',
@@ -226,7 +243,6 @@ return array(
'password must be composed of at least 4 characters' => 'le mot de passe doit être composé d\'au moins 4 caractères',
'payment module %s is not a Thelia\Module\PaymentModuleInterface' => 'Le module de paiement %s n\'est pas une instance de Thelia\Module\PaymentModuleInterface ',
'quantity value is not valid' => 'la valeur de la quantité n\'est pas valide',
'shipping area name' => 'Nom de la zone de livraison',
'this product id does not exists : %d' => 'l\'id du produit %d n\'existe pas',
'time format' => 'Format d\'heure',
);

View File

@@ -14,6 +14,7 @@ namespace Thelia\Controller\Admin;
use Thelia\Core\Security\Resource\AdminResources;
use Thelia\Core\Security\AccessManager;
use Thelia\Model\Module;
use Thelia\Model\ModuleQuery;
use Thelia\Core\Template\TemplateHelper;
use Thelia\Core\Template\TemplateDefinition;
@@ -25,13 +26,45 @@ use Thelia\Tools\URL;
*/
class TranslationsController extends BaseAdminController
{
/**
* @param string $item_name the modume code
* @return Module the module object
* @throws \InvalidArgumentException if module was not found
*/
protected function getModule($item_name) {
if (null !== $module = ModuleQuery::create()->findPk($item_name))
return $module;
throw new \InvalidArgumentException(
$this->getTranslator()->trans("No module found for code '%item'", ['%item' => $item_name])
);
}
protected function getModuleTemplateNames(Module $module, $template_type) {
$templates =
TemplateHelper::getInstance()->getList(
$template_type,
$module->getAbsoluteTemplateBasePath()
);
$names = [];
foreach($templates as $template) $names[] = $template->getName();
return $names;
}
protected function renderTemplate()
{
// Get related strings, if all input data are here
$item_to_translate = $this->getRequest()->get('item_to_translate');
$item_id = $this->getRequest()->get('item_id', '');
$item_name = $this->getRequest()->get('item_name', '');
if ($item_to_translate == 'mo' && ! empty($item_name))
$module_part = $this->getRequest()->get('module_part', '');
else
$module_part = false;
$all_strings = array();
@@ -41,51 +74,106 @@ class TranslationsController extends BaseAdminController
$templateArguments = array(
'item_to_translate' => $item_to_translate,
'item_id' => $item_id,
'item_name' => $item_name,
'module_part' => $module_part,
'view_missing_traductions_only' => $this->getRequest()->get('view_missing_traductions_only', 0),
'max_input_vars_warning' => false,
);
// Find the i18n directory, and the directory to examine.
if (! empty($item_id) || $item_to_translate == 'co') {
if (! empty($item_name) || $item_to_translate == 'co') {
switch ($item_to_translate) {
// Module core
case 'mo' :
if (null !== $module = ModuleQuery::create()->findPk($item_id)) {
$module = $this->getModule($item_name);
if ($module_part == 'core') {
$directory = $module->getAbsoluteBaseDir();
$domain = $module->getTranslationDomain();
$i18n_directory = $module->getAbsoluteI18nPath();
$walkMode = TemplateHelper::WALK_MODE_PHP;
}
else if ($module_part == 'admin-includes') {
$directory = $module->getAbsoluteAdminIncludesPath();
$domain = $module->getAdminIncludesTranslationDomain();
$i18n_directory = $module->getAbsoluteAdminIncludesI18nPath();
$walkMode = TemplateHelper::WALK_MODE_TEMPLATE;
}
else if (! empty($module_part)) {
// Front or back office template, form of $module_part is [bo|fo].subdir-name
list($type, $subdir) = explode('.', $module_part);
if ($type == 'bo') {
$directory = $module->getAbsoluteBackOfficeTemplatePath($subdir);
$domain = $module->getBackOfficeTemplateTranslationDomain($subdir);
$i18n_directory = $module->getAbsoluteBackOfficeI18nTemplatePath($subdir);
}
else if ($type == 'fo') {
$directory = $module->getAbsoluteFrontOfficeTemplatePath($subdir);
$domain = $module->getFrontOfficeTemplateTranslationDomain($subdir);
$i18n_directory = $module->getAbsoluteFrontOfficeI18nTemplatePath($subdir);
}
else {
throw new \InvalidArgumentException("Undefined module template type: '$type'.");
}
$walkMode = TemplateHelper::WALK_MODE_TEMPLATE;
}
// List front and back office templates defined by this module
$templateArguments['back_office_templates'] =
$this->getModuleTemplateNames($module, TemplateDefinition::BACK_OFFICE);
$templateArguments['front_office_templates'] =
$this->getModuleTemplateNames($module, TemplateDefinition::FRONT_OFFICE);
break;
// Thelia Core
case 'co' :
$directory = THELIA_ROOT . 'core/lib/Thelia';
$domain = 'core';
$i18n_directory = THELIA_ROOT . 'core/lib/Thelia/Config/I18n';
$walkMode = TemplateHelper::WALK_MODE_PHP;
break;
// Front-office template
case 'fo' :
$template = new TemplateDefinition($item_id, TemplateDefinition::FRONT_OFFICE);
$template = new TemplateDefinition($item_name, TemplateDefinition::FRONT_OFFICE);
break;
// Back-office template
case 'bo' :
$template = new TemplateDefinition($item_id, TemplateDefinition::BACK_OFFICE);
$template = new TemplateDefinition($item_name, TemplateDefinition::BACK_OFFICE);
break;
// PDF templates
case 'pf' :
$template = new TemplateDefinition($item_id, TemplateDefinition::PDF);
$template = new TemplateDefinition($item_name, TemplateDefinition::PDF);
break;
// Email templates
case 'ma' :
$template = new TemplateDefinition($item_id, TemplateDefinition::EMAIL);
$template = new TemplateDefinition($item_name, TemplateDefinition::EMAIL);
break;
default:
/*
throw new \InvalidArgumentException(
$this->getTranslator()->trans("Undefined translation type: %item", ['%item' => $item_to_translate])
);
*/
}
if ($template) {
$directory = $template->getAbsolutePath();
$i18n_directory = $template->getAbsoluteI18nPath();
$domain = $template->getTranslationDomain();
}
// Load strings to translate
@@ -102,11 +190,11 @@ class TranslationsController extends BaseAdminController
if (! empty($texts)) {
$file = sprintf("%s/%s.php", $i18n_directory, $this->getCurrentEditionLocale());
$file = sprintf("%s".DS."%s.php", $i18n_directory, $this->getCurrentEditionLocale());
$translations = $this->getRequest()->get('translation', array());
TemplateHelper::getInstance()->writeTranslation($file, $texts, $translations);
TemplateHelper::getInstance()->writeTranslation($file, $texts, $translations, true);
if ($save_mode == 'stay')
$this->redirectToRoute("admin.configuration.translations", $templateArguments);
@@ -122,6 +210,7 @@ class TranslationsController extends BaseAdminController
$walkMode,
$this->getTranslator(),
$this->getCurrentEditionLocale(),
$domain,
$all_strings
);

View File

@@ -166,6 +166,7 @@ class Module extends BaseI18nLoop implements PropelSearchLoopInterface
public function parseResults(LoopResult $loopResult)
{
/** @var \Thelia\Model\Module $module */
foreach ($loopResult->getResultDataCollection() as $module) {
$loopResultRow = new LoopResultRow($module);
$loopResultRow->set("ID", $module->getId())
@@ -199,13 +200,13 @@ class Module extends BaseI18nLoop implements PropelSearchLoopInterface
/* if not ; test if it uses admin inclusion : module_configuration.html */
if (false === $hasConfigurationInterface) {
if (file_exists( sprintf("%s/AdminIncludes/%s.html", $module->getAbsoluteBaseDir(), "module_configuration"))) {
if (file_exists($module->getAbsoluteAdminIncludesPath() . DS . "module_configuration.html")) {
$hasConfigurationInterface = true;
}
}
} else {
// Make a quick and dirty test on the module's routing.xml file
$routing = @file_get_contents($module->getAbsoluteBaseDir() . DS . "Config" . DS . "routing.xml");
$routing = @file_get_contents($module->getAbsoluteConfigPath() . DS . "routing.xml");
if ($routing && strpos($routing, '/admin/module/') !== false) {
$hasConfigurationInterface = true;

View File

@@ -80,8 +80,8 @@ class AdminUtilities extends AbstractSmartyPlugin
<a href="{url path='/admin/configuration/currencies/positionDown' currency_id=$ID}"><i class="icon-arrow-down"></i></a>
*/
if ($permissions == null || $this->securityContext->isGranted(
"ADMIN",
if ($this->securityContext->isGranted(
array("ADMIN"),
$resource === null ? array() : array($resource),
$module === null ? array() : array($module),
array($access))

View File

@@ -63,13 +63,14 @@ class Module extends AbstractSmartyPlugin
$modules = ModuleQuery::getActivated();
/** @var \Thelia\Model\Module $module */
foreach ($modules as $module) {
if (null !== $moduleLimit && $moduleLimit != $module->getCode()) {
continue;
}
$file = sprintf("%s/AdminIncludes/%s.html", $module->getAbsoluteBaseDir(), $location);
$file = $module->getAbsoluteAdminIncludesPath() . DS . $location . '.html';
if (file_exists($file)) {

View File

@@ -19,41 +19,59 @@ use Symfony\Component\Translation\TranslatorInterface;
class Translation extends AbstractSmartyPlugin
{
protected $translator;
protected $defaultTranslationDomain = '';
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* Set the default translation domain
*
* @param array $params
* @param \Smarty_Internal_Template $smarty
* @return string
*/
public function setDefaultTranslationDomain($params, &$smarty)
{
$this->defaultTranslationDomain = $this->getParam($params, 'domain');
}
/**
* Process translate function
*
* @param unknown $params
* @param unknown $smarty
* @param array $params
* @param \Smarty_Internal_Template $smarty
* @return string
*/
public function translate($params, &$smarty)
{
// All parameters other than 'l' are supposed to be variables. Build an array of var => value pairs
// All parameters other than 'l' and 'd' are supposed to be variables. Build an array of var => value pairs
// and pass it to the translator
$vars = array();
foreach ($params as $name => $value) {
if ($name != 'l') $vars["%$name"] = $value;
if ($name != 'l' && $name != 'd') $vars["%$name"] = $value;
}
return $this->translator->trans($this->getParam($params, 'l'), $vars);
}
return $this->translator->trans(
$this->getParam($params, 'l'),
$vars,
$this->getParam($params, 'd', $this->defaultTranslationDomain)
);
}
/**
* Define the various smarty plugins hendled by this class
* Define the various smarty plugins handled by this class
*
* @return an array of smarty plugin descriptors
* @return SmartyPluginDescriptor[] an array of smarty plugin descriptors
*/
public function getPluginDescriptors()
{
return array(
new SmartyPluginDescriptor('function', 'intl', $this, 'translate'),
new SmartyPluginDescriptor('function', 'default_translation_domain', $this, 'setDefaultTranslationDomain'),
);
}
}

View File

@@ -46,6 +46,8 @@ class TemplateDefinition
*/
protected $type;
protected $translationDomainPrefix;
public function __construct($name, $type)
{
$this->name = $name;
@@ -54,22 +56,31 @@ class TemplateDefinition
switch ($type) {
case TemplateDefinition::FRONT_OFFICE:
$this->path = self::FRONT_OFFICE_SUBDIR . DS . $name;
$this->translationDomainPrefix = 'fo.';
break;
case TemplateDefinition::BACK_OFFICE:
$this->path = self::BACK_OFFICE_SUBDIR . DS . $name;
$this->translationDomainPrefix = 'bo.';
break;
case TemplateDefinition::PDF:
$this->path = self::PDF_SUBDIR . DS . $name;
$this->translationDomainPrefix = 'pdf.';
break;
case TemplateDefinition::EMAIL:
$this->path = self::EMAIL_SUBDIR . DS . $name;
$this->translationDomainPrefix = 'email.';
break;
default:
$this->path = $name;
$this->translationDomainPrefix = 'generic.';
break;
}
}
public function getTranslationDomain() {
return $this->translationDomainPrefix . strtolower($this->getName());
}
public function getName()
{
return $this->name;

View File

@@ -12,6 +12,7 @@
namespace Thelia\Core\Template;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Model\ConfigQuery;
use Thelia\Log\Tlog;
use Thelia\Core\Translation\Translator;
@@ -96,9 +97,10 @@ class TemplateHelper
* Return a list of existing templates for a given template type
*
* @param int $templateType the template type
* @return An array of \Thelia\Core\Template\TemplateDefinition
* @param string the template base (module or core, default to core).
* @return TemplateDefinition[] of \Thelia\Core\Template\TemplateDefinition
*/
public function getList($templateType)
public function getList($templateType, $base = THELIA_TEMPLATE_DIR)
{
$list = $exclude = array();
@@ -108,24 +110,29 @@ class TemplateHelper
if ($templateType == $type) {
$baseDir = THELIA_TEMPLATE_DIR.$subdir;
$baseDir = rtrim($base, DS).DS.$subdir;
// Every subdir of the basedir is supposed to be a template.
$di = new \DirectoryIterator($baseDir);
try {
// Every subdir of the basedir is supposed to be a template.
$di = new \DirectoryIterator($baseDir);
foreach ($di as $file) {
// Ignore 'dot' elements
if ($file->isDot() || ! $file->isDir()) continue;
foreach ($di as $file) {
// Ignore 'dot' elements
if ($file->isDot() || ! $file->isDir()) continue;
// Ignore reserved directory names
if (in_array($file->getFilename(), $exclude)) continue;
// Ignore reserved directory names
if (in_array($file->getFilename(), $exclude)) continue;
$list[] = new TemplateDefinition($file->getFilename(), $templateType);
$list[] = new TemplateDefinition($file->getFilename(), $templateType);
}
}
catch (\UnexpectedValueException $ex) {
// Ignore the exception and continue
}
return $list;
}
}
return $list;
}
protected function normalizePath($path)
@@ -152,11 +159,12 @@ class TemplateHelper
* @param string $walkMode type of file scanning: WALK_MODE_PHP or WALK_MODE_TEMPLATE
* @param \Thelia\Core\Translation\Translator $translator the current translator
* @param string $currentLocale the current locale
* @param string $domain the translation domain (fontoffice, backoffice, module, etc...)
* @param array $strings the list of strings
* @throws \InvalidArgumentException if $walkMode contains an invalid value
* @return number the total number of translatable texts
*/
public function walkDir($directory, $walkMode, Translator $translator, $currentLocale, &$strings)
public function walkDir($directory, $walkMode, Translator $translator, $currentLocale, $domain, &$strings)
{
$num_texts = 0;
@@ -165,7 +173,7 @@ class TemplateHelper
$allowed_exts = array('php');
} elseif ($walkMode == self::WALK_MODE_TEMPLATE) {
$prefix = '\{intl[\s]l=';
$prefix = "\\{intl(?:.*?)l=";
$allowed_exts = array('html', 'tpl', 'xml');
} else {
@@ -178,11 +186,12 @@ class TemplateHelper
Tlog::getInstance()->debug("Walking in $directory, in mode $walkMode");
/** @var \DirectoryIterator $fileInfo */
foreach (new \DirectoryIterator($directory) as $fileInfo) {
if ($fileInfo->isDot()) continue;
if ($fileInfo->isDir()) $num_texts += $this->walkDir($fileInfo->getPathName(), $walkMode, $translator, $currentLocale, $strings);
if ($fileInfo->isDir()) $num_texts += $this->walkDir($fileInfo->getPathName(), $walkMode, $translator, $currentLocale, $domain, $strings);
if ($fileInfo->isFile()) {
@@ -219,7 +228,7 @@ class TemplateHelper
$strings[$hash] = array(
'files' => array($short_path),
'text' => $match,
'translation' => $translator->trans($match, array(), 'messages', $currentLocale, false),
'translation' => $translator->trans($match, array(), $domain, $currentLocale, false),
'dollar' => strstr($match, '$') !== false
);
}
@@ -233,13 +242,24 @@ class TemplateHelper
return $num_texts;
} catch (\UnexpectedValueException $ex) {
echo $ex;
// Directory does not exists => ignore/
}
}
public function writeTranslation($file, $texts, $translations)
public function writeTranslation($file, $texts, $translations, $createIfNotExists = false)
{
$fs = new Filesystem();
if (! $fs->exists($file) && true === $createIfNotExists) {
$dir = dirname($file);
if (! $fs->exists($file)) {
$fs->mkdir($dir);
}
}
if ($fp = @fopen($file, 'w')) {
fwrite($fp, '<' . "?php\n\n");

View File

@@ -34,6 +34,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Config\DatabaseConfiguration;
use Thelia\Config\DefinePropel;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Template\TemplateDefinition;
use Thelia\Core\DependencyInjection\Loader\XmlFileLoader;
@@ -43,6 +44,7 @@ use Propel\Runtime\Propel;
use Propel\Runtime\Connection\ConnectionManagerSingle;
use Thelia\Core\Template\TemplateHelper;
use Thelia\Log\Tlog;
use Thelia\Model\Module;
class Thelia extends Kernel
{
@@ -93,8 +95,8 @@ class Thelia extends Kernel
/**
* Add all module's standard templates to the parser environment
*
* @param TheliaParser $parser the parser
* @param Module $module the Module.
* @param ParserInterface $parser the parser
* @param Module $module the Module.
*/
protected function addStandardModuleTemplatesToParserEnvironment($parser, $module)
{
@@ -108,10 +110,10 @@ class Thelia extends Kernel
/**
* Add a module template directory to the parser environment
*
* @param TheliaParser $parser the parser
* @param Module $module the Module.
* @param string $templateType the template type (one of the TemplateDefinition type constants)
* @param string $templateSubdirName the template subdirectory name (one of the TemplateDefinition::XXX_SUBDIR constants)
* @param ParserInterface $parser the parser
* @param Module $module the Module.
* @param string $templateType the template type (one of the TemplateDefinition type constants)
* @param string $templateSubdirName the template subdirectory name (one of the TemplateDefinition::XXX_SUBDIR constants)
*/
protected function addModuleTemplateToParserEnvironment($parser, $module, $templateType, $templateSubdirName)
{
@@ -168,8 +170,11 @@ class Thelia extends Kernel
$modules = \Thelia\Model\ModuleQuery::getActivated();
$translationDirs = array();
/** @var ParserInterface $parser */
$parser = $container->getDefinition('thelia.parser');
/** @var Module $module */
foreach ($modules as $module) {
try {
@@ -190,14 +195,21 @@ class Thelia extends Kernel
} else {
$container->addCompilerPass($compiler);
}
}
$loader = new XmlFileLoader($container, new FileLocator($module->getAbsoluteConfigPath()));
$loader->load("config.xml");
$moduleDomain = strtolower($module->getCode());
// Core module translation
if (is_dir($dir = $module->getAbsoluteI18nPath())) {
$translationDirs[] = $dir;
$translationDirs[$module->getTranslationDomain()] = $dir;
}
// Admin includes translation
if (is_dir($dir = $module->getAbsoluteAdminIncludesI18nPath())) {
$translationDirs[$module->getAdminIncludesTranslationDomain()] = $dir;
}
$this->addStandardModuleTemplatesToParserEnvironment($parser, $module);
@@ -207,16 +219,16 @@ class Thelia extends Kernel
}
}
// Load translation from templates
// core translation
$translationDirs[] = THELIA_ROOT . "core/lib/Thelia/Config/I18n";
// Load core translation
$translationDirs['core'] = THELIA_ROOT . "core/lib/Thelia/Config/I18n";
// Standard templates (front, back, pdf, mail)
$th = TemplateHelper::getInstance();
/** @var TemplateDefinition $templateDefinition */
foreach ($th->getStandardTemplateDefinitions() as $templateDefinition) {
if (is_dir($dir = $templateDefinition->getAbsoluteI18nPath())) {
$translationDirs[] = $dir;
$translationDirs[$templateDefinition->getTranslationDomain()] = $dir;
}
}
@@ -230,14 +242,19 @@ class Thelia extends Kernel
{
$translator = $container->getDefinition('thelia.translator');
$finder = Finder::create()
->files()
->depth(0)
->in($dirs);
foreach ($dirs as $domain => $dir) {
foreach ($finder as $file) {
list($locale, $format) = explode('.', $file->getBaseName(), 2);
$translator->addMethodCall('addResource', array($format, (string) $file, $locale));
$finder = Finder::create()
->files()
->depth(0)
->in($dir);
/** @var \DirectoryIterator $file */
foreach ($finder as $file) {
list($locale, $format) = explode('.', $file->getBaseName(), 2);
$translator->addMethodCall('addResource', array($format, (string) $file, $locale, $domain));
}
}
}

View File

@@ -14,6 +14,7 @@ namespace Thelia\Core\Translation;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Translator as BaseTranslator;
use Thelia\Log\Tlog;
class Translator extends BaseTranslator
{
@@ -63,7 +64,7 @@ class Translator extends BaseTranslator
*
* @api
*/
public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null, $return_default_if_not_available = true)
public function trans($id, array $parameters = array(), $domain = 'core', $locale = null, $return_default_if_not_available = true)
{
if (null === $locale) {
$locale = $this->getLocale();
@@ -73,11 +74,19 @@ class Translator extends BaseTranslator
$this->loadCatalogue($locale);
}
if ($this->catalogues[$locale]->has((string) $id, $domain))
if (! $this->catalogues[$locale]->has((string) $id, $domain)) {
}
if ($this->catalogues[$locale]->has((string) $id, $domain)) {
return parent::trans($id, $parameters, $domain, $locale);
else if ($return_default_if_not_available)
return strtr($id, $parameters);
else
return '';
} else {
Tlog::getInstance()->addWarning("Undefined translation: locale: $locale, domain: $domain, ID: $id");
if ($return_default_if_not_available)
return strtr($id, $parameters);
else
return '';
}
}
}

View File

@@ -1,95 +1,179 @@
<?php
namespace Thelia\Model;
use Propel\Runtime\Connection\ConnectionInterface;
use Thelia\Model\Base\Module as BaseModule;
use Thelia\Model\Tools\ModelEventDispatcherTrait;
class Module extends BaseModule
{
use ModelEventDispatcherTrait;
use \Thelia\Model\Tools\PositionManagementTrait;
public function postSave(ConnectionInterface $con = null)
{
ModuleQuery::resetActivated();
}
/**
* @return the module's base directory path, relative to THELIA_MODULE_DIR
*/
public function getBaseDir()
{
return ucfirst($this->getCode());
}
/**
* @return the module's base directory path, relative to THELIA_MODULE_DIR
*/
public function getAbsoluteBaseDir()
{
return THELIA_MODULE_DIR . $this->getBaseDir();
}
/**
* @return the module's config directory path, relative to THELIA_MODULE_DIR
*/
public function getConfigPath()
{
return $this->getBaseDir() . DS . "Config";
}
/**
* @return the module's config absolute directory path
*/
public function getAbsoluteConfigPath()
{
return THELIA_MODULE_DIR . $this->getConfigPath();
}
/**
* @return the module's i18N directory path, relative to THELIA_MODULE_DIR
*/
public function getI18nPath()
{
return $this->getBaseDir() . DS . "I18n";
}
/**
* @return the module's i18N absolute directory path
*/
public function getAbsoluteI18nPath()
{
return THELIA_MODULE_DIR . $this->getI18nPath();
}
/**
* Return the absolute path to one of the module's template directories
*
* @param int $templateSubdirName the name of the, probably one of TemplateDefinition::xxx_SUBDIR constants
*/
public function getAbsoluteTemplateDirectoryPath($templateSubdirName)
{
return sprintf("%s%stemplates%s%s", $this->getAbsoluteBaseDir(), DS, DS, $templateSubdirName);
}
/**
* Calculate next position relative to module type
*/
protected function addCriteriaToPositionQuery($query)
{
$query->filterByType($this->getType());
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
return true;
}
}
<?php
namespace Thelia\Model;
use Propel\Runtime\Connection\ConnectionInterface;
use Thelia\Core\Template\TemplateDefinition;
use Thelia\Model\Base\Module as BaseModule;
use Thelia\Model\Tools\ModelEventDispatcherTrait;
use Thelia\Model\Tools\PositionManagementTrait;
class Module extends BaseModule
{
use ModelEventDispatcherTrait;
use PositionManagementTrait;
const ADMIN_INCLUDES_DIRECTORY_NAME = "AdminIncludes";
public function postSave(ConnectionInterface $con = null)
{
ModuleQuery::resetActivated();
}
public function getTranslationDomain() {
return strtolower($this->getCode());
}
public function getAdminIncludesTranslationDomain() {
return $this->getTranslationDomain().'.ai';
}
public function getAbsoluteBackOfficeTemplatePath($subdir)
{
return sprintf("%s".DS."%s".DS."%s",
$this->getAbsoluteTemplateBasePath(),
TemplateDefinition::BACK_OFFICE_SUBDIR,
$subdir
);
}
public function getAbsoluteBackOfficeI18nTemplatePath($subdir)
{
return sprintf("%s".DS."%s".DS."%s",
$this->getAbsoluteI18nPath(),
TemplateDefinition::BACK_OFFICE_SUBDIR,
$subdir
);
}
public function getBackOfficeTemplateTranslationDomain($templateName) {
return $this->getTranslationDomain(). '.bo.' . $templateName;
}
public function getAbsoluteFrontOfficeTemplatePath($subdir)
{
return sprintf("%s".DS."%s".DS."%s",
$this->getAbsoluteTemplateBasePath(),
TemplateDefinition::FRONT_OFFICE_SUBDIR,
$subdir
);
}
public function getAbsoluteFrontOfficeI18nTemplatePath($subdir)
{
return sprintf("%s".DS."%s".DS."%s",
$this->getAbsoluteI18nPath(),
TemplateDefinition::FRONT_OFFICE_SUBDIR,
$subdir
);
}
public function getFrontOfficeTemplateTranslationDomain($templateName) {
return $this->getTranslationDomain(). '.fo.' . $templateName;
}
/**
* @return the module's base directory path, relative to THELIA_MODULE_DIR
*/
public function getBaseDir()
{
return ucfirst($this->getCode());
}
/**
* @return the module's base directory path, relative to THELIA_MODULE_DIR
*/
public function getAbsoluteBaseDir()
{
return THELIA_MODULE_DIR . $this->getBaseDir();
}
/**
* @return the module's config directory path, relative to THELIA_MODULE_DIR
*/
public function getConfigPath()
{
return $this->getBaseDir() . DS . "Config";
}
/**
* @return the module's config absolute directory path
*/
public function getAbsoluteConfigPath()
{
return THELIA_MODULE_DIR . $this->getConfigPath();
}
/**
* @return the module's i18N directory path, relative to THELIA_MODULE_DIR
*/
public function getI18nPath()
{
return $this->getBaseDir() . DS . "I18n";
}
/**
* @return the module's i18N absolute directory path
*/
public function getAbsoluteI18nPath()
{
return THELIA_MODULE_DIR . $this->getI18nPath();
}
/**
* @return the module's AdminIncludes absolute directory path
*/
public function getAbsoluteAdminIncludesPath()
{
return $this->getAbsoluteBaseDir() . DS . self::ADMIN_INCLUDES_DIRECTORY_NAME;
}
/**
* @return the module's AdminIncludes i18N absolute directory path
*/
public function getAbsoluteAdminIncludesI18nPath()
{
return THELIA_MODULE_DIR . $this->getI18nPath() . DS . self::ADMIN_INCLUDES_DIRECTORY_NAME;
}
/**
* Return the absolute path to the module's template directory
*
* @return string a path
*/
public function getAbsoluteTemplateBasePath()
{
return $this->getAbsoluteBaseDir() . DS . 'templates';
}
/**
* Return the absolute path to one of the module's template directories
*
* @param int $templateSubdirName the name of the, probably one of TemplateDefinition::xxx_SUBDIR constants
* @return string a path
*/
public function getAbsoluteTemplateDirectoryPath($templateSubdirName)
{
return $this->getAbsoluteTemplateBasePath() .DS. $templateSubdirName;
}
/**
* Calculate next position relative to module type
*/
protected function addCriteriaToPositionQuery($query)
{
$query->filterByType($this->getType());
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
return true;
}
}

View File

@@ -10,6 +10,9 @@
{* -- Declare assets directory, relative to template base directory --------- *}
{declare_assets directory='assets'}
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
<!DOCTYPE html>
<html lang="{$lang_code}">
<head>

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
{* Update an Address *}
{form name="thelia.lang.update"}

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
{loop name="product_edit" type="product" visible="*" id=$product_id backend_context="1" lang=$edit_language_id}
<div class="form-container">

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
{loop name="product_edit" type="product" visible="*" id=$product_id backend_context="1" lang=$edit_language_id}
<div class="form-container">

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
<div class="form-group">
{ifloop rel="free_attributes"}
<form method="POST" action="{url path='/admin/configuration/templates/attributes/add'}">

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
<div class="form-group">
{ifloop rel="free_features"}
<form method="POST" action="{url path='/admin/configuration/templates/features/add'}">

View File

@@ -1,4 +1,7 @@
{* this temlate is loaded via Ajax in the login page, to prevent login page slowdown *}
{* this template is loaded via Ajax in the login page, to prevent login page slowdown *}
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='bo.default'}
<div class="panel-group" id="accordion">
{loop type="feed" name="thelia_feeds" url="http://thelia.net/Flux-rss.html?id_rubrique=8" limit="3"}

View File

@@ -1,8 +1,11 @@
#order
# Maximum number of lines in lists
# --------------------------------
max_displayed_orders = 20
max_displayed_customers = 20
#order status
# order status - seems ununsed ?
# ------------------------------
order_not_paid = 'warning'
order_paid = 'success'
order_processing = 'primary'

View File

@@ -1,320 +1,347 @@
{extends file="admin-layout.tpl"}
{block name="page-title"}{intl l='Translations'}{/block}
{block name="check-resource"}admin.configuration.variable{/block}
{block name="check-access"}update{/block}
{block name="main-content"}
<div class="variables translations">
<div id="wrapper" class="container">
<ul class="breadcrumb">
<li><a href="{url path='/admin/home'}">{intl l="Home"}</a></li>
<li><a href="{url path='/admin/configuration'}">{intl l="Configuration"}</a></li>
<li><a href="{url path='/admin/configuration/translations'}">{intl l="Translations"}</a></li>
</ul>
<div class="row">
<div class="col-md-12 general-block-decorator">
<div class="row">
<div class="col-md-12 title title-without-tabs">
{intl l="Translation"}
</div>
<div class="form-container">
<div class="col-md-12">
<form method="POST" id="translation_form" action="{url path='/admin/configuration/translations/update'}">
<fieldset>
{include
file = "includes/inner-form-toolbar.html"
close_url = {url path='/admin/configuration'}
}
<input type="hidden" name="edit_language_id" value="{$edit_language_id}" />
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label class="control-label" for="item_to_translate">{intl l='Select which items you want to translate'}</label>
<select id="item_to_translate" required="required" name="item_to_translate" class="submit-on-change form-control">
<option value="">{intl l='Please select items to translate'}</option>
<option value="fo" {if $item_to_translate=='fo'}selected="selected"{/if}>{intl l='Front-office templates'}</option>
<option value="pf" {if $item_to_translate=='pf'}selected="selected"{/if}>{intl l='PDF templates'}</option>
<option value="ma" {if $item_to_translate=='ma'}selected="selected"{/if}>{intl l='E-mail templates'}</option>
<option value="bo" {if $item_to_translate=='bo'}selected="selected"{/if}>{intl l='Back-office templates'}</option>
<option value="mo" {if $item_to_translate=='mo'}selected="selected"{/if}>{intl l='Modules'}</option>
<option value="co" {if $item_to_translate=='co'}selected="selected"{/if}>{intl l='Thelia core'}</option>
</select>
</div>
</div>
{if $item_to_translate == 'mo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_id">{intl l='Select the module you want to translate'}</label>
<select id="item_id" required="required" name="item_id" class="submit-on-change form-control">
<option value="">{intl l='Please select the module to translate'}</option>
{loop type="module" name="translate-module" backend_context=1}
<option value="{$ID}" {if $item_id == $ID}selected="selected"{/if}>{$TITLE}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'fo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_id">{intl l='Select the front-office template you want to translate'}</label>
<select id="item_id" required="required" name="item_id" class="submit-on-change form-control">
<option value="">{intl l='Please select the F.O. template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="front-office" backend_context=1}
<option value="{$NAME}" {if $item_id == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'bo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_id">{intl l='Select the back-office template you want to translate'}</label>
<select id="item_id" required="required" name="item_id" class="submit-on-change form-control">
<option value="">{intl l='Please select the B.O. template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="back-office" backend_context=1}
<option value="{$NAME}" {if $item_id == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'ma'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_id">{intl l='Select the E-mail template you want to translate'}</label>
<select id="item_id" required="required" name="item_id" class="submit-on-change form-control">
<option value="">{intl l='Please select the E-mail template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="email" backend_context=1}
<option value="{$NAME}" {if $item_id == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'pf'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_id">{intl l='Select the PDF template you want to translate'}</label>
<select id="item_id" required="required" name="item_id" class="submit-on-change form-control">
<option value="">{intl l='Please select the PDF template to translate'}</option>
{loop type="template" name="translate-pdf-template" template-type="pdf" backend_context=1}
<option value="{$NAME}" {if $item_id == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{/if}
<div class="col-md-4">
<div class="form-group">
<label class="control-label">&nbsp;</label>
<label class="checkbox control-label">
<input class="submit-on-change" type="checkbox" name="view_missing_traductions_only" value="1" {if $view_missing_traductions_only}checked="checked"{/if}> {intl l='View only missing translations'}
</label>
</div>
</div>
</div>
</fieldset>
{* -- STRINGS TO TRANSLATE --------------------------------------------- *}
{$currfile = false}
{$close_last_panel = false}
{$idx = 0}
{foreach $all_strings as $info}
<input type="hidden" id="text_{$idx}" name="text[]" value="{$info.text}" />
{$not_translated = empty($info.translation)}
{if $view_missing_traductions_only != 1 || $not_translated }
{* Create a liste of files names *}
{if count($info.files) > 1}
{$label = {intl l='In pages:'}}
{capture name="banner" assign=file_names}
<ul>
{foreach $info.files as $file}
<li>{$file}</li>
{/foreach}
</ul>
{/capture}
{else}
{$label = {intl l='In page'}}
{$file_names = $info.files.0}
{/if}
{* Display current file liste, if required *}
{if $file_names != $currfile}
{if $currfile !== false}
{* Close current panel *}
</tbody></table></div>
{$close_last_panel = false}
{/if}
{$currfile = $file_names}
<div class="panel panel-default">
<div class="panel-heading">
{loop name="lang_list" type="lang" id=$edit_language_id}
<img src="{image file="assets/img/flags/{$CODE}.png"}" alt="{$TITLE}" />
{/loop}
{$label} {$file_names nofilter}
</div>
<table class="table table-condensed table-hover">
<tbody>
{$close_last_panel = true}
{/if}
<tr>
<td class="col-md-6">
{$info.text}
{if $info.dollar}
<div>
<span class="label label-warning">{intl l='Warning'}</span>
{intl l='Il seems that this string contains a Smarty variable (&#36;). If \'s the case, it cannot be transleted properly.'}
</div>
{/if}
</td>
<td class="col-md-6" >
<div class="input-group {if $not_translated}has-error{/if}">
<span class="input-group-addon"><a href="#" data-text-num="{$idx}" class="copy-translation" title="{intl l='Copy source text in input field'}"><span class="glyphicon glyphicon-chevron-right"></span></a></span>
<input type="text" id="translation_{$idx}" class="translation_field form-control" name="translation[]" value="{$info.translation}" />
</div>
</td>
</tr>
{else}
{* Text is not displayed, put it in a hidden field *}
<input type="hidden" id="translation_{$idx}" name="translation[]" value="{$info.translation}" />
{/if}
{$idx = $idx + 1}
{/foreach}
{if isset($all_strings) }
{if empty($all_strings) }
<div class="alert alert-info">
{intl l='<strong>Did not found any text to translate</strong>. It\'s probably normal. If not, please be sure to use Smarty\'s "intl" function in templates, or the Translator::trans() method in PHP files.'}
</div>
{else if $view_missing_traductions_only == 1 && $currfile == false }
<div class="alert alert-success">
{intl l='<strong>Congratulations</strong>, all text is now translated !'}
</div>
{/if}
{if $close_last_panel}
{* close the last panel *}
</tbody></table></div>
{/if}
{/if}
{if $max_input_vars_warning}
<div class="alert alert-danger">
{intl l='<strong>Cannot translate all fields.</strong> According to your PHP configuration, forms cannot contains more than %current_max_input_vars input fields, but at least %required_max_input_vars are required. Please change the value of max_input_vars in your PHP configuration of change the translation file by hand.'
current_max_input_vars=$current_max_input_vars
required_max_input_vars=$required_max_input_vars
}
</div>
{/if}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/block}
{block name="javascript-initialization"}
<script>
$(function() {
var translation_changed = false;
$('#item_to_translate').change(function() {
$('#item-id').val('');
$('.item-id-selector').hide();
});
$('.submit-on-change').change(function() {
$('#translation_form').submit();
});
$('input.translation_field').change(function() { translation_changed = true; });
$('.copy-translation').click(function(ev) {
var num = $(this).data('text-num');
$('#translation_' + num).val($('#text_' + num).val());
translation_changed = true;
ev.preventDefault();
})
$('#translation_form').submit(function(ev) {
if (translation_changed
&&
! confirm("{intl l='Some of your translations are not saved. Continue anyway ?'}") ) {
ev.preventDefault();
}
});
// Intercept language changes, to reload string when changing language
$('.form-submit-button').click(function(ev) {
// For a submit throught the save buttons, do not show the change warning.
translation_changed = false;
});
// Intercept language changes, to reload string when changing language
$('.language-change-button').click(function(ev) {
$('input[name=edit_language_id]').val($(this).data('language-id'));
$('#translation_form').submit();
ev.preventDefault();
});
});
</script>
{/block}
{block name="javascript-last-call"}
{module_include location='translations-js'}
{/block}
{extends file="admin-layout.tpl"}
{block name="page-title"}{intl l='Translations'}{/block}
{block name="check-resource"}admin.configuration.variable{/block}
{block name="check-access"}update{/block}
{block name="main-content"}
<div class="variables translations">
<div id="wrapper" class="container">
<ul class="breadcrumb">
<li><a href="{url path='/admin/home'}">{intl l="Home"}</a></li>
<li><a href="{url path='/admin/configuration'}">{intl l="Configuration"}</a></li>
<li><a href="{url path='/admin/configuration/translations'}">{intl l="Translations"}</a></li>
</ul>
<div class="row">
<div class="col-md-12 general-block-decorator">
<div class="row">
<div class="col-md-12 title title-without-tabs">
{intl l="Translation"}
</div>
<div class="form-container">
<div class="col-md-12">
<form method="POST" id="translation_form" action="{url path='/admin/configuration/translations/update'}">
<fieldset>
{include
file = "includes/inner-form-toolbar.html"
close_url = {url path='/admin/configuration'}
}
<input type="hidden" name="edit_language_id" value="{$edit_language_id}" />
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label class="control-label" for="item_to_translate">{intl l='Items to translate'}</label>
<select id="item_to_translate" required="required" name="item_to_translate" class="submit-on-change form-control">
<option value="">{intl l='Please select items to translate'}</option>
<option value="fo" {if $item_to_translate=='fo'}selected="selected"{/if}>{intl l='Front-office templates'}</option>
<option value="pf" {if $item_to_translate=='pf'}selected="selected"{/if}>{intl l='PDF templates'}</option>
<option value="ma" {if $item_to_translate=='ma'}selected="selected"{/if}>{intl l='E-mail templates'}</option>
<option value="bo" {if $item_to_translate=='bo'}selected="selected"{/if}>{intl l='Back-office templates'}</option>
<option value="mo" {if $item_to_translate=='mo'}selected="selected"{/if}>{intl l='Modules'}</option>
<option value="co" {if $item_to_translate=='co'}selected="selected"{/if}>{intl l='Thelia core'}</option>
</select>
</div>
</div>
{if $item_to_translate == 'mo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_name">{intl l='Module you want to translate'}</label>
<select id="item_name" required="required" name="item_name" class="submit-on-change form-control">
<option value="">{intl l='Please select the module to translate'}</option>
{loop type="module" name="translate-module" backend_context=1}
<option value="{$ID}" {if $item_name == $ID}selected="selected"{/if}>{$TITLE}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'fo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_name">{intl l='Front-office template you want to translate'}</label>
<select id="item_name" required="required" name="item_name" class="submit-on-change form-control">
<option value="">{intl l='Please select the F.O. template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="front-office" backend_context=1}
<option value="{$NAME}" {if $item_name == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'bo'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_name">{intl l='Back-office template you want to translate'}</label>
<select id="item_name" required="required" name="item_name" class="submit-on-change form-control">
<option value="">{intl l='Please select the B.O. template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="back-office" backend_context=1}
<option value="{$NAME}" {if $item_name == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'ma'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_name">{intl l='E-mail template you want to translate'}</label>
<select id="item_name" required="required" name="item_name" class="submit-on-change form-control">
<option value="">{intl l='Please select the E-mail template to translate'}</option>
{loop type="template" name="translate-fo-template" template-type="email" backend_context=1}
<option value="{$NAME}" {if $item_name == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{else if $item_to_translate == 'pf'}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="item_name">{intl l='PDF template you want to translate'}</label>
<select id="item_name" required="required" name="item_name" class="submit-on-change form-control">
<option value="">{intl l='Please select the PDF template to translate'}</option>
{loop type="template" name="translate-pdf-template" template-type="pdf" backend_context=1}
<option value="{$NAME}" {if $item_name == $NAME}selected="selected"{/if}>{$NAME}</option>
{/loop}
</select>
</div>
</div>
{/if}
{if $item_to_translate == 'mo' && $item_name != ''}
<div class="col-md-4 item-id-selector">
<div class="form-group">
<label class="control-label" for="module_part">{intl l='Module component'}</label>
<select id="module_part" required="required" name="module_part" class="submit-on-change form-control">
<option value="">{intl l='Please select the module component'}</option>
<option value="core" {if $module_part == 'core'}selected="selected"{/if}>Core files</option>
<option value="admin-includes" {if $module_part == 'admin-includes'}selected="selected"{/if}>Administration includes template</option>
{foreach $back_office_templates as $template}
{$option_value = "bo.{$template}"}
<option value="{$option_value}" {if $module_part == $option_value}selected="selected"{/if}>Back-office template "{$template}"</option>
{/foreach}
{foreach $front_office_templates as $template}
{$option_value = "fo.{$template}"}
<option value="{$option_value}" {if $module_part == $option_value}selected="selected"{/if}>Front-office template "{$template}"</option>
{/foreach}
</select>
</div>
</div>
{/if}
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="checkbox control-label">
<input class="submit-on-change" type="checkbox" name="view_missing_traductions_only" value="1" {if $view_missing_traductions_only}checked="checked"{/if}> {intl l='View only missing translations.'}
</label>
</div>
</div>
</div>
</fieldset>
{* -- STRINGS TO TRANSLATE --------------------------------------------- *}
{$currfile = false}
{$close_last_panel = false}
{$idx = 0}
{foreach $all_strings as $info}
<input type="hidden" id="text_{$idx}" name="text[]" value="{$info.text}" />
{$not_translated = empty($info.translation)}
{if $view_missing_traductions_only != 1 || $not_translated }
{* Create a liste of files names *}
{if count($info.files) > 1}
{$label = {intl l='In pages:'}}
{capture name="banner" assign=file_names}
<ul>
{foreach $info.files as $file}
<li>{$file}</li>
{/foreach}
</ul>
{/capture}
{else}
{$label = {intl l='In page'}}
{$file_names = $info.files.0}
{/if}
{* Display current file liste, if required *}
{if $file_names != $currfile}
{if $currfile !== false}
{* Close current panel *}
</tbody></table></div>
{$close_last_panel = false}
{/if}
{$currfile = $file_names}
<div class="panel panel-default">
<div class="panel-heading">
{loop name="lang_list" type="lang" id=$edit_language_id}
<img src="{image file="assets/img/flags/{$CODE}.png"}" alt="{$TITLE}" />
{/loop}
{$label} {$file_names nofilter}
</div>
<table class="table table-condensed table-hover">
<tbody>
{$close_last_panel = true}
{/if}
<tr>
<td class="col-md-6">
{$info.text}
{if $info.dollar}
<div>
<span class="label label-warning">{intl l='Warning'}</span>
{intl l='Il seems that this string contains a Smarty variable (&#36;). If \'s the case, it cannot be transleted properly.'}
</div>
{/if}
</td>
<td class="col-md-6" >
<div class="input-group {if $not_translated}has-error{/if}">
<span class="input-group-addon"><a href="#" data-text-num="{$idx}" class="copy-translation" title="{intl l='Copy source text in input field'}"><span class="glyphicon glyphicon-chevron-right"></span></a></span>
<input type="text" id="translation_{$idx}" class="translation_field form-control" name="translation[]" value="{$info.translation}" />
</div>
</td>
</tr>
{else}
{* Text is not displayed, put it in a hidden field *}
<input type="hidden" id="translation_{$idx}" name="translation[]" value="{$info.translation}" />
{/if}
{$idx = $idx + 1}
{/foreach}
{if isset($all_strings) }
{if empty($all_strings) }
<div class="alert alert-info">
{intl l='<strong>Did not found any text to translate</strong>. It\'s probably normal. If not, please be sure to use Smarty\'s "intl" function in templates, or the Translator::trans() method in PHP files.'}
</div>
{else if $view_missing_traductions_only == 1 && $currfile == false }
<div class="alert alert-success">
{intl l='<strong>Congratulations</strong>, all text is now translated !'}
</div>
{/if}
{if $close_last_panel}
{* close the last panel *}
</tbody></table></div>
{/if}
{/if}
{if $max_input_vars_warning}
<div class="alert alert-danger">
{intl l='<strong>Cannot translate all fields.</strong> According to your PHP configuration, forms cannot contains more than %current_max_input_vars input fields, but at least %required_max_input_vars are required. Please change the value of max_input_vars in your PHP configuration of change the translation file by hand.'
current_max_input_vars=$current_max_input_vars
required_max_input_vars=$required_max_input_vars
}
</div>
{/if}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/block}
{block name="javascript-initialization"}
<script>
$(function() {
var translation_changed = false;
$('#item_to_translate').change(function() {
$('#item-id').val('');
$('.item-id-selector').hide();
});
$('#item_name').change(function() {
$('#module_part').val('');
});
$('.submit-on-change').change(function() {
$('#translation_form').submit();
});
$('input.translation_field').change(function() { translation_changed = true; });
$('.copy-translation').click(function(ev) {
var num = $(this).data('text-num');
$('#translation_' + num).val($('#text_' + num).val());
translation_changed = true;
ev.preventDefault();
})
$('#translation_form').submit(function(ev) {
if (translation_changed
&&
! confirm("{intl l='Some of your translations are not saved. Continue anyway ?'}") ) {
ev.preventDefault();
}
});
// Intercept language changes, to reload string when changing language
$('.form-submit-button').click(function(ev) {
// For a submit throught the save buttons, do not show the change warning.
translation_changed = false;
});
// Intercept language changes, to reload string when changing language
$('.language-change-button').click(function(ev) {
$('input[name=edit_language_id]').val($(this).data('language-id'));
$('#translation_form').submit();
ev.preventDefault();
});
});
</script>
{/block}
{block name="javascript-last-call"}
{module_include location='translations-js'}
{/block}

View File

@@ -1,3 +1,6 @@
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='fo.default'}
{form name="thelia.order.delivery"}
{loop type="delivery" name="deliveries" force_return="true" country=$country}

View File

@@ -1,449 +1,451 @@
<!doctype html>
<!--
______ __ __ ______ __ __ ______
/\__ _\ /\ \_\ \ /\ ___\ /\ \ /\ \ /\ __ \
\/_/\ \/ \ \ __ \ \ \ __\ \ \ \____ \ \ \ \ \ __ \
\ \_\ \ \_\ \_\ \ \_____\ \ \_____\ \ \_\ \ \_\ \_\
\/_/ \/_/\/_/ \/_____/ \/_____/ \/_/ \/_/\/_/
Copyright (c) OpenStudio
email : info@thelia.net
web : http://www.thelia.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the
GNU General Public License : http://www.gnu.org/licenses/
-->
{* Declare assets directory, relative to template base directory *}
{declare_assets directory='assets'}
{block name="no-return-functions"}{/block}
{assign var="store_name" value="{config key="store_name"}"}
{if not $store_name}{assign var="store_name" value="{intl l='Thelia V2'}"}{/if}
{* paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither *}
<!--[if lt IE 7 ]><html class="no-js oldie ie6" lang="{lang attr="code"}"> <![endif]-->
<!--[if IE 7 ]><html class="no-js oldie ie7" lang="{lang attr="code"}"> <![endif]-->
<!--[if IE 8 ]><html class="no-js oldie ie8" lang="{lang attr="code"}"> <![endif]-->
<!--[if (gte IE 9)|!(IE)]><!--><html lang="{lang attr="code"}" class="no-js"> <!--<![endif]-->
<head>
{* Test if javascript is enabled *}
<script>(function(H) { H.className=H.className.replace(/\bno-js\b/,'js') } )(document.documentElement);</script>
<meta charset="utf-8">
{* Page Title *}
<title>{block name="page-title"}{strip}{if $page_title}{$page_title}{elseif $breadcrumbs}{foreach from=$breadcrumbs|array_reverse item=breadcrumb}{$breadcrumb.title|unescape} - {/foreach}{$store_name}{/if}{/strip}{/block}</title>
{* Meta Tags *}
<meta name="generator" content="{intl l='Thelia V2'}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
{block name="meta"}
<meta name="description" content="{if $page_description}{$page_description}{else}{$store_name}{/if}">
<meta name="robots" content="noindex,nofollow">
{/block}
{* Stylesheets *}
{stylesheets file='assets/css/styles.css'}
<link rel="stylesheet" href="{$asset_url}">
{/stylesheets}
{block name="stylesheet"}{/block}
{* Favicon *}
{images file='assets/img/favicon.ico'}<link rel="shortcut icon" type="image/x-icon" href="{$asset_url}">{/images}
{images file='assets/img/favicon.png'}<link rel="icon" type="image/png" href="{$asset_url}" />{/images}
{* HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries *}
<!--[if lt IE 9]>
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body class="{block name="body-class"}{/block}" itemscope itemtype="http://schema.org/WebPage">
<!-- Accessibility -->
<a class="sr-only" href="#content">{intl l="Skip to content"}</a>
<div class="page" role="document">
<div class="header-container" itemscope itemtype="http://schema.org/WPHeader">
<div class="navbar" itemscope itemtype="http://schema.org/SiteNavigationElement">
<div class="container">
<div class="navbar-header">
<!-- .navbar-toggle is used as the toggle for collapsed navbar content -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".nav-main">
<span class="sr-only">{intl l="Toggle navigation"}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{navigate to="index"}">{$store_name}</a>
</div>
<!-- Place everything within .nav-collapse to hide it until above 768px -->
<nav class="navbar-collapse collapse nav-main" role="navigation" aria-label="{intl l="Main Navigation"}">
{nocache}
<ul class="nav navbar-nav navbar-cart navbar-right">
{loop type="auth" name="customer_info_block" role="CUSTOMER"}
<li><a href="{url path="/logout"}" class="logout">{intl l="Log out!"}</a></li>
<li><a href="{url path="/account"}" class="account">{intl l="My Account"}</a></li>
{/loop}
{elseloop rel="customer_info_block"}
<li><a href="{url path="/register"}" class="register">{intl l="Register!"}</a></li>
<li class="dropdown">
<a href="{url path="/login"}" class="login">{intl l="Log In!"}</a>
<div class="dropdown-menu">
{form name="thelia.front.customer.login"}
<form id="form-login-mini" action="{url path="/login"}" method="post" {form_enctype form=$form}>
{form_hidden_fields form=$form}
{form_field form=$form field="success_url"}
<input type="hidden" name="{$name}" value="{navigate to="current"}">
{/form_field}
{form_field form=$form field="email"}
<div class="form-group group-email">
<label for="{$label_attr.for}-mini">{intl l="Email address"}</label>
<input type="email" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" aria-required="true" required>
</div>
{/form_field}
{form_field form=$form field="password"}
<div class="form-group group-password">
<label for="{$label_attr.for}-mini">{intl l="Password"}</label>
<input type="password" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" aria-required="true" required>
</div>
{/form_field}
{form_field form=$form field="account"}
<input type="hidden" name="{$name}" value="1">
{/form_field}
<div class="group-btn">
<button type="submit" class="btn btn-login-mini">{intl l="Sign In"}</button>
<a href="{url path="/register"}" class="btn btn-register-mini">{intl l="Register"}</a>
</div>
</form>
{/form}
</div>
</li>
{/elseloop}
{include file="includes/mini-cart.html" nocache}
</ul>
{/nocache}
<ul class="nav navbar-nav navbar-categories">
<li><a href="{navigate to="index"}" class="home">{intl l="Home"}</a></li>
{loop type="category" name="category.navigation" parent="0"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
</ul>
</nav>
</div>
</div>
<header class="container" role="banner">
<div class="header">
<h1 class="logo">
<a href="{navigate to="index"}" title="{$store_name}">
{images file='assets/img/logo.gif'}<img src="{$asset_url}" alt="{$store_name}">{/images}
</a>
</h1>
<div class="language-container">
<div class="search-container">
<form id="form-search" action="{url path="/search"}" method="get" role="search" aria-labelledby="search-label">
<label id="search-label" for="q">{intl l="Search a product"}</label>
<div class="input-group">
<input type="search" name="q" id="q" placeholder="{intl l="Search..."}" class="form-control" autocomplete="off" aria-required="true" required pattern=".{ldelim}2,{rdelim}" title="{intl l="Minimum 2 characters."}">
<div class="input-group-btn">
<button type="submit" class="btn btn-search"><i class="icon-search"></i> <span>{intl l="Search"}</span></button>
</div>
</div>
</form>
</div>
<div class="language-switch" aria-labelledby="language-label" role="form">
<span id="language-label" class="dropdown-label">{intl l="Language:"}</span>
<a class="current dropdown-toggle" data-toggle="dropdown" href="{url path="/language"}">{lang attr="title"}</a>
<ul class="select dropdown-menu">
{loop type="lang" name="lang_available" exclude="{lang attr="id"}"}
<li><a href="{url path="{navigate to="current"}" lang={$CODE}}">{$TITLE}</a></li>
{/loop}
</ul>
</div>
<div class="currency-switch" aria-labelledby="currency-label" role="form">
<span id="currency-label" class="dropdown-label">{intl l="Currency:"}</span>
<a class="current dropdown-toggle" data-toggle="dropdown" href="{url path="/currency"}">{currency attr="code"}</a>
<ul class="select dropdown-menu">
{loop type="currency" name="currency_available" exclude="{currency attr="id"}" }
<li><a href="{url path="{navigate to="current"}" currency={$ISOCODE}}">{$SYMBOL} - {$NAME}</a></li>
{/loop}
</ul>
</div>
</div>
</div>
</header><!-- /.header -->
</div><!-- /.header-container -->
<main class="main-container" role="main">
<div class="container">
{block name="breadcrumb"}{include file="misc/breadcrumb.tpl"}{/block}
<div id="content">{block name="main-content"}{/block}</div>
</div><!-- /.container -->
</main><!-- /.main-container -->
<section class="footer-container" itemscope itemtype="http://schema.org/WPFooter">
<section class="footer-banner">
<div class="container">
<div class="banner banner-col-3">
<div class="col">
<span class="icon-truck icon-flip-horizontal"></span>
{intl l="Free shipping"} <small>{intl l="Orders over $50"}</small>
</div>
<div class="col">
<span class="icon-credit-card"></span>
{intl l="Secure payment"} <small>{intl l="Multi-payment platform"}</small>
</div>
<div class="col">
<span class="icon-info"></span>
{intl l="Need help ?"} <small>{intl l="Questions ? See our F.A.Q."}</small>
</div>
</div>
</div>
</section><!-- /.footer-banner -->
<section class="footer-block">
<div class="container">
<div class="blocks block-col-4">
<div class="col">
<section class="block block-links">
<div class="block-heading"><h3 class="block-title">{intl l="Latest articles"}</h3></div>
<div class="block-content">
{ifloop rel="blog.articles"}
<ul>
{loop type="content" name="blog.articles" folder="1" limit="3"}
<li>
<a href="{$URL}">
<h4 class="block-subtitle">{$TITLE}</h4>
<p>{$CHAPO}</p>
</a>
</li>
{/loop}
</ul>
{/ifloop}
{elseloop rel="blog.articles"}
<ul>
<li>{intl l="No articles currently"}</li>
</ul>
{/elseloop}
</div>
</section>
</div>
<div class="col">
<section class="block block-default">
<div class="block-heading"><h3 class="block-title">{intl l="Useful links"}</h3></div>
<div class="block-content">
<ul>
{loop name="footer_links" type="content" folder="2"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
{loop type="auth" name="customer_is_logged" role="CUSTOMER"}
<li><a href="{url path="/logout"}" class="logout">{intl l="Log out!"}</a></li>
<li><a href="{url path="/account"}" class="account">{intl l="My Account"}</a></li>
{/loop}
{elseloop rel="customer_is_logged"}
<li><a href="{url path="/login"}">{intl l="Login"}</a></li>
<li><a href="{url path="/register"}">{intl l="Register"}</a></li>
{/elseloop}
<li><a href="{url path="/cart"}">{intl l="Cart"}</a></li>
<li><a href="{url path="/order/delivery"}">{intl l="Checkout"}</a></li>
</ul>
</div>
</section>
</div>
<div class="col">
<section class="block block-social">
<div class="block-heading"><h3 class="block-title">{intl l="Follow us"}</h3></div>
<div class="block-content">
<p>{intl l="Follow us introduction"}</p>
<ul role="presentation">
<li>
<a href="http://facebook.com" rel="nofollow" class="facebook" data-toggle="tooltip" data-placement="top" title="{intl l="Facebook"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-facebook icon-light"></span>
</span>
<span class="visible-print">{intl l="Facebook"}</span>
</a>
</li>
<li>
<a href="https://twitter.com" rel="nofollow" class="twitter" data-toggle="tooltip" data-placement="top" title="{intl l="Twitter"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-twitter icon-light"></span>
</span>
<span class="visible-print">{intl l="Twitter"}</span>
</a>
</li>
<li>
<a href="http://instagram.com" rel="nofollow" class="instagram" data-toggle="tooltip" data-placement="top" title="{intl l="Instagram"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-instagram icon-light"></span>
</span>
<span class="visible-print">{intl l="Instagram"}</span>
</a>
</li>
<li>
<a href="http://www.google.com" rel="nofollow" class="google-plus" data-toggle="tooltip" data-placement="top" title="{intl l="Google+"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-google-plus icon-light"></span>
</span>
<span class="visible-print">{intl l="Google+"}</span>
</a>
</li>
<li>
<a href="http://www.youtube.com" rel="nofollow" class="youtube" data-toggle="tooltip" data-placement="top" title="{intl l="Youtube"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-youtube icon-light"></span>
</span>
<span class="visible-print">{intl l="Youtube"}</span>
</a>
</li>
<li>
<a href="#rss" class="rss" rel="nofollow" data-toggle="tooltip" data-placement="top" title="{intl l="RSS"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-rss icon-light"></span>
</span>
<span class="visible-print">{intl l="RSS"}</span>
</a>
</li>
</ul>
</div>
</section>
<section class="block block-newsletter">
<div class="block-heading"><h3 class="block-title">{intl l="Newsletter"}</h3></div>
<div class="block-content">
<p id="newsletter-describe">{intl l="Sign up to receive our latest news."}</p>
{form name="thelia.front.newsletter"}
<form id="form-newsletter-mini" action="{url path="/newsletter"}" method="post">
{form_hidden_fields form=$form}
{form_field form=$form field="email"}
<div class="form-group">
<label for="{$label_attr.for}-mini">{intl l="Email address"}</label>
<input type="email" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" placeholder="{intl l="Your email address"}" aria-describedby="newsletter-describe" {if $required} aria-required="true" required{/if} autocomplete="off">
</div>
{/form_field}
<button type="submit" class="btn btn-subscribe">{intl l="Subscribe"}</button>
</form>
{/form}
</div>
</section>
</div>
<div class="col">
<section class="block block-contact" itemscope itemtype="http://schema.org/Organization">
<div class="block-heading"><h3 class="block-title">{intl l="Contact Us"}</h3></div>
<div class="block-content">
<meta itemprop="name" content="{$store_name}">
<ul>
<li class="contact-address">
<address class="adr" itemprop="address" itemscope itemtype="http://schema.org/PostalAddress">
<span class="street-address" itemprop="streetAddress">{config key="store_address1"} {config key="store_address2"} {config key="store_address3"}</span><br>
<span class="postal-code" itemprop="postalCode">{config key="store_zipcode"}</span>
<span class="locality" itemprop="addressLocality">
{config key="store_city"}
{if {config key="store_country"} }
{loop type="country" name="address.country.title" id={config key="store_country"}}, <span class="country-name">{$TITLE}</span>{/loop}
{/if}
</span>
</address>
</li>
{if {config key="store_phone"} }
<li class="contact-phone">
<span class="tel" itemprop="telephone">{config key="store_phone"}</span>
</li>
{/if}
{if {config key="store_email"} }
<li class="contact-email">
{mailto address={config key="store_email"} encode="hex" extra='class="email" itemprop="email"'}
</li>
{/if}
</ul>
</div>
</section>
</div>
</div>
</div>
</section><!-- /.footer-block -->
<footer class="footer-info" role="contentinfo">
<div class="container">
<div class="info">
<nav class="nav-footer" role="navigation">
<ul>
{loop name="footer_links" type="content" folder="2"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
{*<li><a href="#">Site Map</a></li>
<li><a href="#">Terms & Conditions</a></li>*}
<li><a href="{url path="/contact"}">{intl l="Contact Us"}</a></li>
</ul>
</nav>
<section class="copyright">{intl l="Copyright"} &copy; <time datetime="{'Y-m-d'|date}">{'Y'|date}</time> <a href="http://thelia.net" rel="external">Thelia</a></section>
</div>
</div>
</footer><!-- /.footer-info -->
</section><!-- /.footer-container -->
</div><!-- /.page -->
{block name="before-javascript-include"}{/block}
<!-- JavaScript -->
<!--[if lt IE 9]><script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <![endif]-->
<!--[if (gte IE 9)|!(IE)]><!--><script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> <!--<![endif]-->
<script>
if (typeof jQuery == 'undefined') {
{javascripts file='assets/js/libs/jquery.js'}
document.write(unescape("%3Cscript src='{$asset_url}' %3E%3C/script%3E"));
{/javascripts}
}
</script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script>
{javascripts file='assets/js/bootstrap/bootstrap.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{javascripts file='assets/js/plugins/bootbox/bootbox.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{block name="after-javascript-include"}{/block}
{block name="javascript-initialization"}{/block}
<!-- Custom scripts -->
{javascripts file='assets/js/script.js'}
<script src="{$asset_url}"></script>
{/javascripts}
</body>
<!doctype html>
<!--
______ __ __ ______ __ __ ______
/\__ _\ /\ \_\ \ /\ ___\ /\ \ /\ \ /\ __ \
\/_/\ \/ \ \ __ \ \ \ __\ \ \ \____ \ \ \ \ \ __ \
\ \_\ \ \_\ \_\ \ \_____\ \ \_____\ \ \_\ \ \_\ \_\
\/_/ \/_/\/_/ \/_____/ \/_____/ \/_/ \/_/\/_/
Copyright (c) OpenStudio
email : info@thelia.net
web : http://www.thelia.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the
GNU General Public License : http://www.gnu.org/licenses/
-->
{* Declare assets directory, relative to template base directory *}
{declare_assets directory='assets'}
{* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *}
{default_translation_domain domain='fo.default'}
{block name="no-return-functions"}{/block}
{assign var="store_name" value="{config key="store_name"}"}
{if not $store_name}{assign var="store_name" value="{intl l='Thelia V2'}"}{/if}
{* paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither *}
<!--[if lt IE 7 ]><html class="no-js oldie ie6" lang="{lang attr="code"}"> <![endif]-->
<!--[if IE 7 ]><html class="no-js oldie ie7" lang="{lang attr="code"}"> <![endif]-->
<!--[if IE 8 ]><html class="no-js oldie ie8" lang="{lang attr="code"}"> <![endif]-->
<!--[if (gte IE 9)|!(IE)]><!--><html lang="{lang attr="code"}" class="no-js"> <!--<![endif]-->
<head>
{* Test if javascript is enabled *}
<script>(function(H) { H.className=H.className.replace(/\bno-js\b/,'js') } )(document.documentElement);</script>
<meta charset="utf-8">
{* Page Title *}
<title>{block name="page-title"}{strip}{if $page_title}{$page_title}{elseif $breadcrumbs}{foreach from=$breadcrumbs|array_reverse item=breadcrumb}{$breadcrumb.title|unescape} - {/foreach}{$store_name}{/if}{/strip}{/block}</title>
{* Meta Tags *}
<meta name="generator" content="{intl l='Thelia V2'}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
{block name="meta"}
<meta name="description" content="{if $page_description}{$page_description}{else}{$store_name}{/if}">
<meta name="robots" content="noindex,nofollow">
{/block}
{* Stylesheets *}
{stylesheets file='assets/css/styles.css'}
<link rel="stylesheet" href="{$asset_url}">
{/stylesheets}
{block name="stylesheet"}{/block}
{* Favicon *}
{images file='assets/img/favicon.ico'}<link rel="shortcut icon" type="image/x-icon" href="{$asset_url}">{/images}
{images file='assets/img/favicon.png'}<link rel="icon" type="image/png" href="{$asset_url}" />{/images}
{* HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries *}
<!--[if lt IE 9]>
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body class="{block name="body-class"}{/block}" itemscope itemtype="http://schema.org/WebPage">
<!-- Accessibility -->
<a class="sr-only" href="#content">{intl l="Skip to content"}</a>
<div class="page" role="document">
<div class="header-container" itemscope itemtype="http://schema.org/WPHeader">
<div class="navbar" itemscope itemtype="http://schema.org/SiteNavigationElement">
<div class="container">
<div class="navbar-header">
<!-- .navbar-toggle is used as the toggle for collapsed navbar content -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".nav-main">
<span class="sr-only">{intl l="Toggle navigation"}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{navigate to="index"}">{$store_name}</a>
</div>
<!-- Place everything within .nav-collapse to hide it until above 768px -->
<nav class="navbar-collapse collapse nav-main" role="navigation" aria-label="{intl l="Main Navigation"}">
{nocache}
<ul class="nav navbar-nav navbar-cart navbar-right">
{loop type="auth" name="customer_info_block" role="CUSTOMER"}
<li><a href="{url path="/logout"}" class="logout">{intl l="Log out!"}</a></li>
<li><a href="{url path="/account"}" class="account">{intl l="My Account"}</a></li>
{/loop}
{elseloop rel="customer_info_block"}
<li><a href="{url path="/register"}" class="register">{intl l="Register!"}</a></li>
<li class="dropdown">
<a href="{url path="/login"}" class="login">{intl l="Log In!"}</a>
<div class="dropdown-menu">
{form name="thelia.front.customer.login"}
<form id="form-login-mini" action="{url path="/login"}" method="post" {form_enctype form=$form}>
{form_hidden_fields form=$form}
{form_field form=$form field="success_url"}
<input type="hidden" name="{$name}" value="{navigate to="current"}">
{/form_field}
{form_field form=$form field="email"}
<div class="form-group group-email">
<label for="{$label_attr.for}-mini">{intl l="Email address"}</label>
<input type="email" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" aria-required="true" required>
</div>
{/form_field}
{form_field form=$form field="password"}
<div class="form-group group-password">
<label for="{$label_attr.for}-mini">{intl l="Password"}</label>
<input type="password" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" aria-required="true" required>
</div>
{/form_field}
{form_field form=$form field="account"}
<input type="hidden" name="{$name}" value="1">
{/form_field}
<div class="group-btn">
<button type="submit" class="btn btn-login-mini">{intl l="Sign In"}</button>
<a href="{url path="/register"}" class="btn btn-register-mini">{intl l="Register"}</a>
</div>
</form>
{/form}
</div>
</li>
{/elseloop}
{include file="includes/mini-cart.html" nocache}
</ul>
{/nocache}
<ul class="nav navbar-nav navbar-categories">
<li><a href="{navigate to="index"}" class="home">{intl l="Home"}</a></li>
{loop type="category" name="category.navigation" parent="0"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
</ul>
</nav>
</div>
</div>
<header class="container" role="banner">
<div class="header">
<h1 class="logo">
<a href="{navigate to="index"}" title="{$store_name}">
{images file='assets/img/logo.gif'}<img src="{$asset_url}" alt="{$store_name}">{/images}
</a>
</h1>
<div class="language-container">
<div class="search-container">
<form id="form-search" action="{url path="/search"}" method="get" role="search" aria-labelledby="search-label">
<label id="search-label" for="q">{intl l="Search a product"}</label>
<div class="input-group">
<input type="search" name="q" id="q" placeholder="{intl l="Search..."}" class="form-control" autocomplete="off" aria-required="true" required pattern=".{ldelim}2,{rdelim}" title="{intl l="Minimum 2 characters."}">
<div class="input-group-btn">
<button type="submit" class="btn btn-search"><i class="icon-search"></i> <span>{intl l="Search"}</span></button>
</div>
</div>
</form>
</div>
<div class="language-switch" aria-labelledby="language-label" role="form">
<span id="language-label" class="dropdown-label">{intl l="Language:"}</span>
<a class="current dropdown-toggle" data-toggle="dropdown" href="{url path="/language"}">{lang attr="title"}</a>
<ul class="select dropdown-menu">
{loop type="lang" name="lang_available" exclude="{lang attr="id"}"}
<li><a href="{url path="{navigate to="current"}" lang={$CODE}}">{$TITLE}</a></li>
{/loop}
</ul>
</div>
<div class="currency-switch" aria-labelledby="currency-label" role="form">
<span id="currency-label" class="dropdown-label">{intl l="Currency:"}</span>
<a class="current dropdown-toggle" data-toggle="dropdown" href="{url path="/currency"}">{currency attr="code"}</a>
<ul class="select dropdown-menu">
{loop type="currency" name="currency_available" exclude="{currency attr="id"}" }
<li><a href="{url path="{navigate to="current"}" currency={$ISOCODE}}">{$SYMBOL} - {$NAME}</a></li>
{/loop}
</ul>
</div>
</div>
</div>
</header><!-- /.header -->
</div><!-- /.header-container -->
<main class="main-container" role="main">
<div class="container">
{block name="breadcrumb"}{include file="misc/breadcrumb.tpl"}{/block}
<div id="content">{block name="main-content"}{/block}</div>
</div><!-- /.container -->
</main><!-- /.main-container -->
<section class="footer-container" itemscope itemtype="http://schema.org/WPFooter">
<section class="footer-banner">
<div class="container">
<div class="banner banner-col-3">
<div class="col">
<span class="icon-truck icon-flip-horizontal"></span>
{intl l="Free shipping"} <small>{intl l="Orders over $50"}</small>
</div>
<div class="col">
<span class="icon-credit-card"></span>
{intl l="Secure payment"} <small>{intl l="Multi-payment platform"}</small>
</div>
<div class="col">
<span class="icon-info"></span>
{intl l="Need help ?"} <small>{intl l="Questions ? See our F.A.Q."}</small>
</div>
</div>
</div>
</section><!-- /.footer-banner -->
<section class="footer-block">
<div class="container">
<div class="blocks block-col-4">
<div class="col">
<section class="block block-links">
<div class="block-heading"><h3 class="block-title">{intl l="Latest articles"}</h3></div>
<div class="block-content">
{ifloop rel="blog.articles"}
<ul>
{loop type="content" name="blog.articles" folder="1" limit="3"}
<li>
<a href="{$URL}">
<h4 class="block-subtitle">{$TITLE}</h4>
<p>{$CHAPO}</p>
</a>
</li>
{/loop}
</ul>
{/ifloop}
{elseloop rel="blog.articles"}
<ul>
<li>{intl l="No articles currently"}</li>
</ul>
{/elseloop}
</div>
</section>
</div>
<div class="col">
<section class="block block-default">
<div class="block-heading"><h3 class="block-title">{intl l="Useful links"}</h3></div>
<div class="block-content">
<ul>
{loop name="footer_links" type="content" folder="2"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
{loop type="auth" name="customer_is_logged" role="CUSTOMER"}
<li><a href="{url path="/logout"}" class="logout">{intl l="Log out!"}</a></li>
<li><a href="{url path="/account"}" class="account">{intl l="My Account"}</a></li>
{/loop}
{elseloop rel="customer_is_logged"}
<li><a href="{url path="/login"}">{intl l="Login"}</a></li>
<li><a href="{url path="/register"}">{intl l="Register"}</a></li>
{/elseloop}
<li><a href="{url path="/cart"}">{intl l="Cart"}</a></li>
<li><a href="{url path="/order/delivery"}">{intl l="Checkout"}</a></li>
</ul>
</div>
</section>
</div>
<div class="col">
<section class="block block-social">
<div class="block-heading"><h3 class="block-title">{intl l="Follow us"}</h3></div>
<div class="block-content">
<p>{intl l="Follow us introduction"}</p>
<ul role="presentation">
<li>
<a href="http://facebook.com" rel="nofollow" class="facebook" data-toggle="tooltip" data-placement="top" title="{intl l="Facebook"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-facebook icon-light"></span>
</span>
<span class="visible-print">{intl l="Facebook"}</span>
</a>
</li>
<li>
<a href="https://twitter.com" rel="nofollow" class="twitter" data-toggle="tooltip" data-placement="top" title="{intl l="Twitter"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-twitter icon-light"></span>
</span>
<span class="visible-print">{intl l="Twitter"}</span>
</a>
</li>
<li>
<a href="http://instagram.com" rel="nofollow" class="instagram" data-toggle="tooltip" data-placement="top" title="{intl l="Instagram"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-instagram icon-light"></span>
</span>
<span class="visible-print">{intl l="Instagram"}</span>
</a>
</li>
<li>
<a href="http://www.google.com" rel="nofollow" class="google-plus" data-toggle="tooltip" data-placement="top" title="{intl l="Google+"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-google-plus icon-light"></span>
</span>
<span class="visible-print">{intl l="Google+"}</span>
</a>
</li>
<li>
<a href="http://www.youtube.com" rel="nofollow" class="youtube" data-toggle="tooltip" data-placement="top" title="{intl l="Youtube"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-youtube icon-light"></span>
</span>
<span class="visible-print">{intl l="Youtube"}</span>
</a>
</li>
<li>
<a href="#rss" class="rss" rel="nofollow" data-toggle="tooltip" data-placement="top" title="{intl l="RSS"}" target="_blank">
<span class="icon-stack">
<span class="icon-circle icon-stack-base"></span>
<span class="icon-rss icon-light"></span>
</span>
<span class="visible-print">{intl l="RSS"}</span>
</a>
</li>
</ul>
</div>
</section>
<section class="block block-newsletter">
<div class="block-heading"><h3 class="block-title">{intl l="Newsletter"}</h3></div>
<div class="block-content">
<p id="newsletter-describe">{intl l="Sign up to receive our latest news."}</p>
{form name="thelia.front.newsletter"}
<form id="form-newsletter-mini" action="{url path="/newsletter"}" method="post">
{form_hidden_fields form=$form}
{form_field form=$form field="email"}
<div class="form-group">
<label for="{$label_attr.for}-mini">{intl l="Email address"}</label>
<input type="email" name="{$name}" id="{$label_attr.for}-mini" class="form-control" maxlength="255" placeholder="{intl l="Your email address"}" aria-describedby="newsletter-describe" {if $required} aria-required="true" required{/if} autocomplete="off">
</div>
{/form_field}
<button type="submit" class="btn btn-subscribe">{intl l="Subscribe"}</button>
</form>
{/form}
</div>
</section>
</div>
<div class="col">
<section class="block block-contact" itemscope itemtype="http://schema.org/Organization">
<div class="block-heading"><h3 class="block-title">{intl l="Contact Us"}</h3></div>
<div class="block-content">
<meta itemprop="name" content="{$store_name}">
<ul>
<li class="contact-address">
<address class="adr" itemprop="address" itemscope itemtype="http://schema.org/PostalAddress">
<span class="street-address" itemprop="streetAddress">{config key="store_address1"} {config key="store_address2"} {config key="store_address3"}</span><br>
<span class="postal-code" itemprop="postalCode">{config key="store_zipcode"}</span>
<span class="locality" itemprop="addressLocality">
{config key="store_city"}
{if {config key="store_country"} }
{loop type="country" name="address.country.title" id={config key="store_country"}}, <span class="country-name">{$TITLE}</span>{/loop}
{/if}
</span>
</address>
</li>
{if {config key="store_phone"} }
<li class="contact-phone">
<span class="tel" itemprop="telephone">{config key="store_phone"}</span>
</li>
{/if}
{if {config key="store_email"} }
<li class="contact-email">
{mailto address={config key="store_email"} encode="hex" extra='class="email" itemprop="email"'}
</li>
{/if}
</ul>
</div>
</section>
</div>
</div>
</div>
</section><!-- /.footer-block -->
<footer class="footer-info" role="contentinfo">
<div class="container">
<div class="info">
<nav class="nav-footer" role="navigation">
<ul>
{loop name="footer_links" type="content" folder="2"}
<li><a href="{$URL}">{$TITLE}</a></li>
{/loop}
{*<li><a href="#">Site Map</a></li>
<li><a href="#">Terms & Conditions</a></li>*}
<li><a href="{url path="/contact"}">{intl l="Contact Us"}</a></li>
</ul>
</nav>
<section class="copyright">{intl l="Copyright"} &copy; <time datetime="{'Y-m-d'|date}">{'Y'|date}</time> <a href="http://thelia.net" rel="external">Thelia</a></section>
</div>
</div>
</footer><!-- /.footer-info -->
</section><!-- /.footer-container -->
</div><!-- /.page -->
{block name="before-javascript-include"}{/block}
<!-- JavaScript -->
<!--[if lt IE 9]><script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <![endif]-->
<!--[if (gte IE 9)|!(IE)]><!--><script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> <!--<![endif]-->
<script>
if (typeof jQuery == 'undefined') {
{javascripts file='assets/js/libs/jquery.js'}
document.write(unescape("%3Cscript src='{$asset_url}' %3E%3C/script%3E"));
{/javascripts}
}
</script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script>
{javascripts file='assets/js/bootstrap/bootstrap.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{javascripts file='assets/js/plugins/bootbox/bootbox.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{block name="after-javascript-include"}{/block}
{block name="javascript-initialization"}{/block}
<!-- Custom scripts -->
{javascripts file='assets/js/script.js'}
<script src="{$asset_url}"></script>
{/javascripts}
</body>
</html>