Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "i18n"

translate messages with html tags

Example:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd">
<xliff version="1.0">
    <file original="global" source-language="en" datatype="plaintext" date="2009-08-12T12:32:00Z">
        <header/>
        <body>
            ...
            <trans-unit>
                <source>from %from_date%&lt;br /&gt;to %to_date%</source>
                <target>de %from_date%&lt;br /&gt;até %to_date%</target>
            </trans-unit>
        </body>
    </file>
</xliff>
 

Use '&lt;' and '&gt;' to replace '<' and '>'. respectively.

by Raphael Araújo on 2012-01-26, tagged i18n  translate  xml 

Tha django-multilingual style for I18n forms (ability to remove selected translations)

This snippet was created under the inspiration of using the django-multilingual package. Also an additional code is used to solve the problem of saving the many-to-many relations in embedded forms (http://trac.symfony-project.org/ticket/5867#comment:21).

The idea is that in all embedded I18n forms appears checkbox "delete" for each translation. When creating a new object this checkbox is checked for all translation form. To use the translation it's necessary to clear the checkbox in this translation form. When you save a form such I18n embedded form is also being validated and saved, and the forms with checkboxes checked are not validated and not saved. Also the objects of I18n embedded forms with checked checkbox are removed.

Probably it's not ideal solution but it works good for me. If you have any idea how to improve it you're always welcome.

The changes in code are only in one file /lib/forms/doctrine/BaseFormDoctrine.class.php:

<?php
 
/**
 * Project form base class.
 *
 * @package    symfony
 * @subpackage form
 * @author     Svyatoslav Mankovski <slawka@slawka.net>
 * @version    SVN: $Id: sfDoctrineFormBaseTemplate.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
 */
abstract class BaseFormDoctrine extends sfFormDoctrine {
 
  protected static $I18nDeleteFieldName = '_i18n_delete';
 
  public function __construct($object = null, $options = array(), $CSRFSecret = null) {
 
    parent::__construct($object, $options, $CSRFSecret);
 
    $this->addI18nDeleteField();
 
    $this->resetFormFields();
  }
 
  public function addI18nDeleteField() {
 
    if ($this->getObject()->getTable()->getParentGenerator() instanceof Doctrine_I18n) {
      $this->widgetSchema[self::$I18nDeleteFieldName] = new sfWidgetFormInputCheckbox(array(
                'label' => sfContext::getInstance()->getI18N()->__('Remove'),
              ));
      $this->setDefault(self::$I18nDeleteFieldName, $this->isNew());
      $this->widgetSchema->moveField(self::$I18nDeleteFieldName, sfWidgetFormSchema::FIRST);
      $this->validatorSchema[self::$I18nDeleteFieldName] = new sfValidatorPass();
    }
 
    return $this;
  }
 
  public function bind(array $taintedValues = null, array $taintedFiles = null) {
 
    if ($this->getObject()->getTable()->hasGenerator('Doctrine_I18n')) {
      $forms = 0;
      $deletes = 0;
      $first_form = '';
      foreach ($taintedValues as $name => $field) {
        if (is_array($field)) {
          $first_form = $first_form == '' ? $name : $first_form;
          $forms++;
          if (isset($field[self::$I18nDeleteFieldName])) {
            $deletes++;
          }
        }
      }
      foreach ($taintedValues as $name => $field) {
        if (is_array($field) && isset($field[self::$I18nDeleteFieldName])) {
          if ($forms > $deletes || $forms == $deletes && $first_form != $name) {
            unset($this->validatorSchema[$name]);
            $this->validatorSchema[$name] = new sfValidatorPass();
          } else {
            unset($taintedValues[$name][self::$I18nDeleteFieldName]);
          }
        }
      }
    }
 
    parent::bind($taintedValues, $taintedFiles);
  }
 
  public function bindAndSave($taintedValues, $taintedFiles = null, $con = null) {
 
    if ($this->getObject()->getTable()->getParentGenerator() instanceof Doctrine_I18n) {
      if (isset($taintedValues[self::$I18nDeleteFieldName])) {
        unset($taintedValues[self::$I18nDeleteFieldName]);
 
        $object = clone $this->getObject();
 
        $object->delete($con);
 
        return true;
      }
    }
 
    return parent::bindAndSave($taintedValues, $taintedFiles, $con);
  }
 
  public function saveEmbeddedForms($con = null, $forms = null, $taintedValues = null) {
    if (null === $con) {
      $con = $this->getConnection();
    }
 
    if (null === $forms) {
      $forms = $this->embeddedForms;
    }
 
    if (null === $taintedValues) {
      $taintedValues = $this->taintedValues;
    }
 
    foreach ($forms as $name => $form) {
      $values = isset($taintedValues[$name]) ? $taintedValues[$name] : array();
      if ($form instanceof sfFormObject) {
        unset($form[self::$CSRFFieldName]);
 
        $form->bindAndSave($values, $this->taintedFiles, $con);
        $form->saveEmbeddedForms($con, null, $values);
      } else {
        $this->saveEmbeddedForms($con, $form->getEmbeddedForms(), $values);
      }
    }
  }
 
}
 
by Svyatoslav Mankovski on 2010-12-10, tagged form  i18n 

Switch culture from any page of your site and improve SEO

CONTEXT

Symfony internationalization is normally managed using session variables. To switch culture you can visit a page that first changes you session variable and then send you back to the page you were visiting, but this potentially breaks your SEO.

Adding culture in every URL is a better solution, but if you want to go back to the page you were visiting, you have to deal with redirection by yourself.

This widget solves the redirection issue by creating localized urls.

CODE

Save your file here: apps/appname/lib/helper/I18nUrlHelper.php

/*
 * Generate a localized version for the URL you are visiting.
 */
 
function localized_current_url($sf_culture = null)
{
  if (! $sf_culture)
  {
    throw new sfException(sprintf('Invalid parameter $sf_culture "%s".', $sf_culture));
  }
 
  $routing    = sfContext::getInstance()->getRouting();
  $request    = sfContext::getInstance()->getRequest();
  $controller = sfContext::getInstance()->getController();
 
  // depending on your routing configuration, you can set $route_name = $routing->getCurrentRouteName()
  $route_name = '';
 
  $parameters = $controller->convertUrlStringToParameters($routing->getCurrentInternalUri());
  $parameters[1]['sf_culture'] = $sf_culture;
 
  return $routing->generate($route_name, array_merge($request->getGetParameters(), $parameters[1]));
}
 

EXAMPLE

Obviously, you must add the :sf_culture token in every rule of your application routing.yml:

product_page:
  url:   /:sf_culture/product/:id
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: product, action: show }
 
homepage:
  url:   /:sf_culture
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: page, action: home }

default_symfony:
  url:   /symfony/:sf_culture/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: default }
 
default_index:
  url:   /:sf_culture/:module
  param: { action: index }
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 
default:
  url:   /:sf_culture/:module/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 

This code let you create a list of link to the localized versions. The list of languages/cultures is taken from your config file.

<div>
  <?php foreach(sfConfig::get('app_languages_available') as $language) { ?>
    <a href="<?php echo localized_current_url($language); ?>"><?php echo $language; ?></a>  
  <?php } ?>
</div>
 
by Davide Fedrigo on 2009-09-28, tagged culture  helper  i18n  navigation  routing 
(3 comments)

Default culture content for i18n table

The table i18n doesn't support default culture. This snippet add the support of the default culture.

This snippet is a port of the snippet "default culture content fallback for i18n tables" for Symfony 1.2

Fallback I18n content

To enable the fallback, edit your table object class in lib/model/TableClassName.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Fetch the i18n object for this object culture.
   *
   * @param   string          $culture    The culture to set
   * @return  mixed                       A i18n object
   * @throws  PropelException             Any exceptions caught during processing will be rethrown wrapped into a PropelException.
   * @link    http://snippets.symfony-project.org/snippet/237 -- modified for Symfony 1.2
   */
  public function getCurrentTableClassNameI18n($culture = null)
  {
    if (is_null($culture)) {
      $culture = is_null($this->culture) ? sfPropel::getDefaultCulture() : $this->culture;
    }
 
    if (!isset($this->current_i18n[$culture]))
    {
      $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $culture);
      if ($obj !== null) { // Test if there is a translation for current culture
        $this->setTableClassNameI18nForCulture($obj, $culture);
      } else { // Create a translation for this culture
        $new_i18n = new TableClassNameI18n();
 
        $default_culture = sfConfig::get('sf_default_culture');
 
        // We try to fetch the default culture translation to initialise the new culture.
        if (!isset($this->current_i18n[$default_culture])) {
          $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $default_culture);
          if ($obj !== null) { // Test if there is a translation for current culture
            $this->setTableClassNameI18nForCulture($obj, $default_culture);
          }
        } else {
          $obj = $this->current_i18n[$default_culture];
        }
 
        if ($obj !== null) {
          $obj->copyInto($new_i18n);
        }
 
        $new_i18n->setId($this->getId());
        $new_i18n->setCulture($culture);
 
        $this->setTableClassNameI18nForCulture($new_i18n, $culture);
      }
    }
 
    return $this->current_i18n[$culture];
  }
 

Saving fallback values at creation

You now need to add default translation when you create a new object. We do that with this doSave function.

To use this function, edit your table object class in lib/model/TableClassName.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Stores the object in the database while setting default culture if necessary.
   *
   * If the object is new, it inserts it; otherwise an update is performed.
   * All related objects are also updated in this method.
   *
   * @param      Connection      $con The database connection
   * @return     int             The number of rows affected by this insert/update and any referring fk objects' save() operations.
   * @throws     PropelException Any exceptions caught during processing will be rethrown wrapped into a PropelException.
   * @see        save()
   * @link       http://snippets.symfony-project.org/snippet/237 -- modified for Symfony 1.2
   */
  protected function doSave(PropelPDO $con)
  {
    $default_culture = sfConfig::get('sf_default_culture');
    $current_culture = is_null($this->culture) ? sfPropel::getDefaultCulture() : $this->culture;
    $obj = null;
 
    // We try to fetch the default culture translation to initialise the new culture.
    if (!isset($this->current_i18n[$default_culture])) {
      $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $default_culture, $con);
      if ($obj !== null) { // Test if there is a translation for current culture
        $this->setTableClassNameI18nForCulture($obj, $default_culture);
      }
    } else {
      $obj = $this->current_i18n[$default_culture];
    }
 
    if($obj === null && isset($this->current_i18n[$current_culture])) {
      $new_i18n = new TableClassNameI18n();
      $this->current_i18n[$current_culture]->copyInto($new_i18n);
 
      $new_i18n->setId($this->getId());
      $new_i18n->setCulture($default_culture);
 
      $this->setTableClassNameI18nForCulture($new_i18n, $default_culture);
    }
 
    return parent::doSave($con);
  }
 

Fetching a list with default translations

To complete this snippet, here is a fallback version of doSelectWithI18n.

To enable the fallback, edit your table object peer class in lib/model/TableClassNamePeer.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Selects a collection of TableClassName objects pre-filled with their i18n objects.
   *
   * @param     Criteria         $criteria   The criteria's object
   * @param     string           $culture    The selected culture.
   * @param     PropelPDO        $con        An optional database connection
   * @return    array                        Array of TableClassName objects.
   * @throws    PropelException              Any exceptions caught during processing will be rethrown wrapped into a PropelException.
   * @link      http://snippets.symfony-project.org/snippet/237 -- modified for Symfony 1.2
   */
  public static function doSelectWithI18n(Criteria $criteria, $culture = null, PropelPDO $con = null)
  {
    $criteria = clone $criteria;
 
    if ($culture === null) {
      $culture = sfContext::getInstance()->getUser()->getCulture();
    }
 
    $default_culture = sfConfig::get('sf_default_culture');
 
    // Set the correct dbName if it has not been overridden
    if ($criteria->getDbName() == Propel::getDefaultDB()) {
      $criteria->setDbName(self::DATABASE_NAME);
    }
 
    TableClassNamePeer::addSelectColumns($c);
    $startcol = (TableClassNamePeer::NUM_COLUMNS - TableClassNamePeer::NUM_LAZY_LOAD_COLUMNS) + 1;
 
    TableClassNameI18nPeer::addSelectColumns($c);
 
    $criteria->addJoin(TableClassNamePeer::ID, TableClassNameI18nPeer::ID);
    $criterion = $criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $culture);
    $criterion->addOr($criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $default_culture));
    $criteria->add($criterion);
 
    $stmt = BasePeer::doSelect($c, $con);
    $results = array();
    $uncultured_results = array();
 
    while($row = $stmt->fetch(PDO::FETCH_NUM)) {
 
      $obj1 = new TableClassName();
      $obj1->hydrate($row);
      $obj1->setCulture($culture);
 
      if(isset($results[$obj1->getId()])) {
        $obj1 = $results[$obj1->getId()];
      }
 
      $omClass = TableClassNameI18nPeer::getOMClass($row, $startcol);
 
      $cls = Propel::importClass($omClass);
      $obj2 = new $cls();
      $obj2->hydrate($row, $startcol);
 
      $obj1->setTableClassNameI18nForCulture($obj2, $obj2->getCulture());
      $obj2->setTableClassName($obj1);
 
      if(!isset($uncultured_results[$obj1->getId()])) {
        $uncultured_results[$obj1->getId()] = $obj1;
      }
 
      if($obj2->getCulture() == $culture) {
        $uncultured_results[$obj1->getId()] = false;
      }
 
      if(!isset($results[$obj1->getId()])) {
        $results[$obj1->getId()] = $obj1;
      } elseif($obj2->getCulture() == $culture) {
        // Move result to the end of results array to fit eventual sort
        // criteria (ugly fix).
        unset($results[$obj1->getId()]);
        $results[$obj1->getId()] = $obj1;
      }
    }
 
    foreach ($uncultured_results as $obj1) {
      if ($obj1) {
        $obj1->setCulture($default_culture);
        $default_culture_object = $obj1->getCurrentTableClassNameI18n();
        if ($default_culture_object) {
          $obj2 = new TableClassNameI18n();
          $default_culture_object->copyInto($obj2);
          $obj2->setCulture($culture);
          $obj2->setTableClassName($obj1);
          $obj1->setTableClassNameI18nForCulture($obj2, $obj2->getCulture());
        }
        $obj1->setCulture($culture);
      }
    }
 
    return array_values($results);
  }
 

Counting results filtered on translations

Before enable the fallback, you must create a constant in your table object peer class in lib/model/TableClassNamePeer.php and replace table by your table name

const COUNT_DISTINCT = 'COUNT(DISTINCT table.ID)';
 

To enable the fallback, edit your table object peer class in lib/model/TableClassNamePeer.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Returns the number of rows matching criteria with I18N criteria.
   *
   * @param       Criteria    $criteria   The criteria's object
   * @param       boolean     $distinct   Whether to select only distinct columns (You can also set DISTINCT modifier in Criteria).
   * @param       Connection  $con        An optional database connection
   * @param       string      $culture    The selected culture.
   * @return      int                     Number of matching rows.
   * @link        http://snippets.symfony-project.org/snippet/237 -- modified for Symfony 1.2
   */
  public static function doCountWithI18n(Criteria $criteria = null, $distinct = false, PropelPDO $con = null, $culture = null)
  {
    // we're going to modify criteria, so copy it first
    if ($criteria === null){
      $criteria = new Criteria();
    } else {
      $criteria = clone $criteria;
    }
 
    $default_culture = sfConfig::get('sf_default_culture');
 
    if ($culture === null) {
      // We use current user culture.
      $culture = sfContext::getInstance()->getUser()->getCulture();
    }
 
    // clear out anything that might confuse the ORDER BY clause
    $criteria->clearSelectColumns()->clearOrderByColumns();
    $criteria->addSelectColumn(TableClassNamePeer::COUNT_DISTINCT);
 
    // just in case we're grouping: add those columns to the select statement
    foreach($criteria->getGroupByColumns() as $column)
    {
      $criteria->addSelectColumn($column);
    }
 
    $criteria->addJoin(TableClassNamePeer::ID, TableClassNameI18nPeer::ID);
    $criterion = $criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $culture);
    $criterion->addOr($criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $default_culture));
    $criteria->add($criterion);
 
    $rs = TableClassNamePeer::doSelectStmt($criteria, $con);
    if ($res = $rs->fetchColumn(0)) {
      return $res;
    } else {
      // no rows returned; we infer that means 0 matches.
      return 0;
    }
  }
 
by Simon Leblanc on 2009-09-20, tagged culture  data  database  i18n  object  propel  symfony12 

A task to extract i18n labels from a form

A piece of code that will extract the existing labels from a form into a xliff file. It will only pick out the labels that have been set by the widgetForm->setLabel() command.

example for UserForm.class.php you can extract it using:

   php symfony i18n:extract-form user
 

.

<?php
 
class i18nExtractFormTask extends sfBaseTask
{
  protected function configure()
  {
    // // add your own arguments here
    // $this->addArguments(array(
    //   new sfCommandArgument('my_arg', sfCommandArgument::REQUIRED, 'My argument'),
    // ));
 
    $this->addOptions(array(
      new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'),
      new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'),
      new sfCommandOption('connection', null, sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'default'),
      new sfCommandOption('source', null, sfCommandOption::PARAMETER_REQUIRED, 'Source language', 'en'),
      // add your own options here
    ));
 
    $this->addArgument('form', sfCommandOption::PARAMETER_REQUIRED, 'Name for the form to translate');
 
 
    $this->namespace        = 'i18n';
    $this->name             = 'extract-form';
    $this->briefDescription = 'Extracts the labels from a form';
    $this->detailedDescription = <<<EOF
The [i18n-extract-form|INFO] task extracts the labels from a form class into a XLIFF file.
Call it with:
   for CoolForm.class.php you can extract it using:
 
  [php symfony i18n-extract-form cool|INFO]
EOF;
  }
 
  protected function execute($arguments = array(), $options = array())
  {
    // initialize the database connection
    $databaseManager = new sfDatabaseManager($this->configuration);
    $connection = $databaseManager->getDatabase($options['connection'] ? $options['connection'] : null)->getConnection();
 
    $sFormname = ucfirst($arguments['form']).'Form';
    $oForm = new $sFormname(false);
    $labels = $oForm->getWidgetSchema()->getLabels();
    $sfMessageSource = new sfMessageSource_XLIFF(sfConfig::get('sf_app_i18n_dir'));
    $sfMessageSource->setCulture($options['source']);
    $sfMessageSource->load($sFormname);
    $existingMessages = $sfMessageSource->read();
    $aKeys = array_keys($existingMessages);
    $aMessages = array_keys($existingMessages[$aKeys[0]]);
    $unLabeled = array();
    foreach ($labels as $name => $value)
    {
        if (!is_null($value))
        {
 
            if (!in_array($value,$aMessages))
            {
                $sfMessageSource->append($value);
                $this->logSection('i18n', "Adding: " . $value);
            }
        } else {
            $unLabeled[] = $name;
        }
    }
 
 
    // an extra service: generate code for the not yet labeled parts: still a bit buggy
    $unLabeled = array_diff($unLabeled, array("_csrf_token"));
    $unLabeled = array_values($unLabeled);
 
       $return = "Code for unlabeled fields.:\n";
       foreach ($unLabeled as $label)
       {
           $return .= "\t\"" . '$this->widgetSchema->setLabel("' . $label .'","")'."\n";
       }
  }
}
 
by Adje on 2009-06-15, tagged form  i18n  xliff 
(1 comment)

Login form in Symfony 1.1, with the new form system

Symfony 1.1 comes with a complete new form system. It works completely according to the MVC draft:

Make sure you have a running Symfony 1.1 based project and application and modules. In this example I build the form inside the myModule module and myLogin action.

My form makes use of i18n, which is in my case autoloaded in settings.yml.

This tutorial uses Symfony 1.1 beta4 and RC1. There are a little important changes with respect to beta3, which I don't cover.

I also expect you to have practical knowledge and a little bit experience with Symfony as system. I will not explain how you i18n implements or modules shield with security.yml.

The form design

The form gets two import fields: username and password. Furthermore is there a hidden field in which the URI comes that the user requested, but got redirected to the loginform (through Symfony’s security.yml and settings.yml). This will be used to go to that URI again after a successful login.

Both imput fields are required I will build a custom validation for a correct username/password check. Also we want to make use of a little new protection feature: CSRF.

The form takes uses i18n for multilinguity, I will use English primarily, but the view has been prepared for other languages.

The start: the action

We will begin with the action which contains the primary control.

/**
 * Executes myLogin action
 *
 * Login functionality.
 *
 * @param void
 * @return void
 * @access public
 */
public function executeMyLogin() {
   // Erase auth data
   $this->getUser()->clearCredentials();
   $this->getUser()->setAuthenticated(FALSE);
 
   // Build login form
   $oForm = new inloggenForm();
 
   if ($this->getRequest()->isMethod('post')) { // When called through POST (form submit)
       $oForm->bind(
           array('username' => $this->getRequest()->getParameter('username'),
                 'password' => $this->getRequest()->getParameter('password'),
                 'referrer' => $this->getRequest()->getParameter('referrer'),
                )
      );
 
      // Save orginal requested location in referrer field
      $oForm->setDefault('referrer', $this->getRequest()->getParameter('referrer'));
      if ($oForm->isValid()) { // When validations OK
          $aValues = $oForm->getValues();
          sfContext::getInstance()->getLogger()->debug($aValues['username']);
 
          // Authentification
          $this->getUser()->setAuthenticated(TRUE);
 
          // To requested page
          $this->redirect($this->getRequest()->getParameter('referrer'));
      }
   } else {
      // Save original requested uri in form
      $oForm->setDefault('referrer', sfContext::getInstance()->getRequest()->getUri());
   }
 
   $this->oForm = $oForm; // form to view
}
 

Logic of authentification is set after POST further. Through the sfForm::bind() method couples the input of the user coupled with the form controller.

The form controller

I made this in myModule/lib/form/inloggenForm.class.php. In the form is defined and coupled with the validations and the formatting.

<?php
class inloggenForm extends sfForm {
    /**
     * Prepare validators
     */
    private function _setValidators() {
        $this->oValGebruikersnaam = new sfValidatorAnd(
                                    array(
                                        new sfValidatorString(array('min_length' => 3, 'max_length' => 50),
                                                              array('min_length' => 'The username should be at least three characters long',
                                                                    'max_length' => 'The username should be fifty characters long at most',
                                                              )
                                        ),
                                        new sfValidatorCallback(array('callback'  => array('cmsLoginValidator', 'execute'),
                                                                      'arguments' => array()
                                                                ),
                                                                array('invalid'  => 'This username/password combination is unknown')
                                        ),
                                    ),
                                    array('required'   => TRUE),
                                    array('required'   => 'The username is mandatory')
        );
        $this->oValWachtwoord = new sfValidatorString(array('required' => TRUE),
                                                      array('required'   => 'The password is mandatory')
        );
        $this->oValReferrer = new sfValidatorString(array('required' => FALSE));
    }
 
    /**
     * Prepare widgets
     */
    private function _setWidgets() {
        $this->oWidGebruikersnaam = new sfWidgetFormInput(array(), array('id' => 'username', 'size' => 25));
        $this->oWidWachtwoord     = new sfWidgetFormInputPassword(array(), array('id' => 'password', 'size' => 10));
        $this->oWidReferrer       = new sfWidgetFormInputHidden(array(), array('id' => 'referrer'));
    }
 
    /**
     * Configure form
     */
    public function configure()     {
        $this->_setValidators();
        $this->_setWidgets();
 
        /*
         * Set validators
         */
        $this->setValidators(array('username' => $this->oValGebruikersnaam,
                                   'password'     => $this->oValWachtwoord,
                                   'referrer'       => $this->oValReferrer,
                             )
        );
 
        /*
         * Set widgets
         */
        $this->setWidgets(array('username' => $this->oWidGebruikersnaam,
                                'password'     => $this->oWidWachtwoord,
                                'referrer'       => $this->oWidReferrer,
                          )
        );
 
        /*
         * Set decorator
         */
        $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
        $this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
        $this->getWidgetSchema()->setFormFormatterName('div');
        $this->getWidgetSchema()->setHelps(array('username' => 'Please enter your username',
                                                 'password' => 'Please enter your password'
                                           )
        );
 
    }
 
    /**
     * Bind override
     */
    public function bind(array $taintedValues = null, array $taintedFiles = array()) {
        $request = sfContext::getInstance()->getRequest();
 
        if ($request->hasParameter(self::$CSRFFieldName))         {
            $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName);
        }
 
        parent::bind($taintedValues, $taintedFiles);
    }
}
 

There are two private methods:

I overloaded the earlier mentioned bind() method to process the CSRF token internally. This fiels is regulated by sfForm internally, you to do need to worry about it much, only with the actual functionality.

The configure() method contains the functionality. The validators are created and linked to the corresponding fields and also are the widgets. Also, the decorator is defined with I will go into later on.

Validators

In _setValidators() sfValidatorBase objects are made for each field.

The password field check is the simplest, only the required input is checked. The object is of the type sfValidatorString (extend sfValidatorBase) without extra controls, only the required attribute with errortext is specified. In principle, you can use each validator as -empty- container.

The username is a combination; it is required, there are restrictions to the the stringlength and a correct login is checked. The stringcontrole/requirement is checked through sfValidatorString, which is quite simple.

The check for a correct login is done through a special validator, that does a callback to an custom function: sfValidatorCallback. This is explained later.

Since these two validators check the same field , the sfValidatorAnd validator is used that combines several validators. All validators must be satisfied. Of course Symfony also offers sfValidatorOr that checks that at least one underlying validator satisfies.

The custom logincheck

As you can see the callback validator calls to a custom class/method: myLoginValidator::execute().

/**
 * Fiel with myLoginValidator klasse
 *
 * @package -
 */
/**
 * Validation correct username/password
 *
 * @author Jordi Backx (Snowkrash)
 * @copyright Copyright © 2008, Jordi Backx (Snowkrash)
 * @package -
 */
class myLoginValidator {
    /**
     * execute validator
     *
     * @param sfValidatorBase Validator instance that calls this method
     * @param string Value of field that sfValidatorCallback checks
     * @param array Arguments for correct working
     *
     * @return value field when OK. Nothing if error (sfValidatorError exception)
     */
    public static function execute ($oValidator, $sValue, $aArguments) {
        if ( /* check OK */ ) { // Return waarde veld indien controle OK
            return $sValue;
        }
        // Throw exception when not OK
        throw new sfValidatorError($oValidator, 'invalid', array('value' => $sValue, 'invalid' => $oValidator->getOption('invalid')));
    }
}
 

I set this here with no further logic, that is application specific, thus that you 'll have to do yourself. The base structure can be used. The three parameters must be defined, otherwise the whole application crashes.

Widgets

In _setWidget() sfWidget objects are made for each field.

The widgets are the form elements: finally <input>, <select> etc tags in combination with labels and errortexts.

Each widget can have HTML attributes, which will be printed inside the form elements.

The view

Finally the form must be printed to the screen through a view template.

<p><?php echo __('You need to log in to be able to use the Content Management System.') ?></p>
<div id="formContainer">
<?php if ($oForm->getErrorSchema()->getErrors()) { ?>
    <div id="formulierFouten">
    <ul>
    <?php foreach ($oForm->getErrorSchema() as $sError) { ?>
        <li><?php echo __($sError) ?></li>
    <?php } ?>
    </ul>
    </div>
<?php } ?>
<form action="<?php echo url_for('myModule/myLogin') ?>" method="post">
    <?php echo $oForm['username']->renderLabel(__($oForm['username']->renderLabelName())); echo $oForm['username']->renderRow(__($oForm->getWidgetSchema()->getHelp('username'))); ?>
    <?php echo $oForm['password']->renderLabel(__($oForm['password']->renderLabelName())); echo $oForm['password']->renderRow(__($oForm->getWidgetSchema()->getHelp('password'))); ?>
    <?php echo $oForm['referrer']->render(array('value' => $oForm->getDefault('referrer'))) ?>
    <?php echo $oForm['_csrf_token'] ?>
    <label for="inloggen">&nbsp;</label><input type="submit" value="Inloggen" id="inloggen" class="aanmeldenSubmit" />
</form>
</div>
 

You can see all the i18n code (__() helper) and some non-Symfony 1.0 form building. Errorlists are built through the errorSchema which is available within the form object, the texts themself can be translated as you can see.

Also the labels and help texts are squeezed through i18n. The field names are in English, because the labels are based on these and must go through i18n. This way everything can be translated.

You can print the whole form with an echo of $oForm (goes through __toString()), but you have more control over the layout when you use specific widgetrender functions, like I do with renderRow(). This method takes the helptext as an argument, with is also translated.

The submit button is no widget, so we place it ourselves the old-fashioned way ... no helper, that is so Symfony 1.0.

CSRF token

That one is new. It is there, but we never defined it. It is created within sfForm and only since beta4 when indicated in settings.yml:

#Form security secret (CSRF protection)
    csrf_secret:             hierjeeigenc0d3     # Unique secret to enable CSRF protection or false to disable
 

You can choose your own code, on which the hash inside the CSRF value is based.

The form functionally is ready, but we want more control over the layout. I am a supporter of the tableless HTML design and the standard formatter of sfForm uses ... tables. Well, we can do better.

The form controller showed the coupling with my own formatter:

/*
 * Set decorator
 */
$oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
$this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
$this->getWidgetSchema()->setFormFormatterName('div');
 

I will now go into this part.

I have a class sfWidgetFormSchemaFormatterDiv in sfWidgetFormSchemaFormatterDiv.class.php made in the application-level lib/ directory so that all modules of can use it.

This takes care of the HTML layout of the form elements.

class sfWidgetFormSchemaFormatterDiv extends sfWidgetFormSchemaFormatter {
    protected
        $rowFormat = '%error%%field%<br />%help%<br />',
        $helpFormat = '<span class="help">%help%</span>',
        $errorRowFormat = '<div>%errors%</div>',
        $errorListFormatInARow = '%errors%',
        $errorRowFormatInARow = '<div class="formError">&darr;&nbsp;%error%&nbsp;&darr;</div>',
        $namedErrorRowFormatInARow = '%name%: %error%<br />',
        $decoratorFormat = '<div id="formContainer">%content%</div>';
}
 

A good article is available that describes this system.

For people that wonder why the label (%label% placeholder) is not used: $rowFormat sets the layout of the renderRow() method and since I want to render the label separately (i18n), it must not be rendered a second time by renderRow().

Conclusion

Hopefully the above can be a good help for your own form in Symfony 1.1. The documentation is quite scarce at the moment, so each bit of help will be welcome.

If the English is somewhat bad, I did a automatic translation of my original Dutch version of the article and tuned that a bit. The reason? I am lazy. ;-)

If you find errors in the above, it is because of copying my code probably. Please mention it in the comments.

Good luck!

by Jordi Backx on 2008-05-10, tagged 11  csrf  decorator  form  formatter  i18n  login  sfform  symfony  validator  widget 

default culture content fallback for i18n tables

By default, I18n content in database does not support fallback in default culture. This snippet allow you to enable I18n content fallback in order to always have a default value for your texts.

This snippet is sponsored by Dorigo consultants.

Fallback I18n content

To enable the fallback, edit your table object class in lib/model/TableClassName.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Fetch the i18n object for this object culture.
   * 
   * @return     mixed A i18n object
   * @throws     PropelException Any exceptions caught during processing will be
   *     rethrown wrapped into a PropelException.
   */
  public function getCurrentTableClassNameI18n()
  {
    if (!isset($this->current_i18n[$this->culture]))
    {
      $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $this->culture);
      if ($obj) // Test if there is a translation for current culture
      {
        $this->setTableClassNameI18nForCulture($obj, $this->culture);
      }
      else // Create a translation for this culture
      {
        $new_i18n = new TableClassNameI18n();
 
        $default_culture = sfConfig::get('sf_i18n_default_culture');
 
        // We try to fetch the default culture translation to initialise the new culture.
        if (!isset($this->current_i18n[$default_culture]))
        {
          $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $default_culture);
          if ($obj) // Test if there is a translation for current culture
          {
            $this->setTableClassNameI18nForCulture($obj, $default_culture);
          }
        }
        else
        {
          $obj = $this->current_i18n[$default_culture];
        }
 
        if ($obj)
        {
          $obj->copyInto($new_i18n);
        }
 
        $new_i18n->setId($this->getId());
        $new_i18n->setCulture($this->culture);
 
        $this->setTableClassNameI18nForCulture($new_i18n, $this->culture);
      }
    }
 
    return $this->current_i18n[$this->culture];
  }
 

Saving fallback values at creation

You now need to add default translation when you create a new object. We do that with this doSave function.

To use this function, edit your table object class in lib/model/TableClassName.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Stores the object in the database while setting default culture if necessary.
   *
   * If the object is new, it inserts it; otherwise an update is performed.
   * All related objects are also updated in this method.
   *
   * @param      Connection $con The database connection
   * @return     int The number of rows affected by this insert/update and any referring fk objects' save() operations.
   * @throws     PropelException Any exceptions caught during processing will be
   *     rethrown wrapped into a PropelException.
   * @see        save()
   */
  protected function doSave($con)
  {
 
    $default_culture = sfConfig::get('sf_i18n_default_culture');
 
    // We try to fetch the default culture translation to initialise the new culture.
    if (!isset($this->current_i18n[$default_culture]))
    {
      $obj = TableClassNameI18nPeer::retrieveByPK($this->getId(), $default_culture, $con);
      if ($obj) // Test if there is a translation for current culture
      {
        $this->setTableClassNameI18nForCulture($obj, $default_culture);
      }
    }
    else
    {
      $obj = $this->current_i18n[$default_culture];
    }
 
    if(!$obj && isset($this->current_i18n[$this->culture]))
    {
      $new_i18n = new TableClassNameI18n();
      $this->current_i18n[$this->culture]->copyInto($new_i18n);
 
      $new_i18n->setId($this->getId());
      $new_i18n->setCulture($default_culture);
 
      $this->setTableClassNameI18nForCulture($new_i18n, $default_culture);
    }
 
    return parent::doSave($con);
  }
 

Fetching a list with default translations

To complete this snippet, here is a fallback version of doSelectWithI18n.

To enable the fallback, edit your table object peer class in lib/model/TableClassNamePeer.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Selects a collection of TableClassName objects pre-filled with their i18n objects.
   *
   * @param      Criteria $criteria
   * @param      string $culture The selected culture.
   * @param      Connection $con An optional database connection
   * @return     array Array of TableClassName objects.
   * @throws     PropelException Any exceptions caught during processing will be
   *     rethrown wrapped into a PropelException.
   */
  public static function doSelectWithI18n(Criteria $c, $culture = null, $con = null)
  {
    if ($culture === null)
    {
      $culture = sfContext::getInstance()->getUser()->getCulture();
    }
 
    $default_culture = sfConfig::get('sf_i18n_default_culture');
 
    // Set the correct dbName if it has not been overridden
    if ($c->getDbName() == Propel::getDefaultDB())
    {
      $c->setDbName(self::DATABASE_NAME);
    }
 
    TableClassNamePeer::addSelectColumns($c);
    $startcol = (TableClassNamePeer::NUM_COLUMNS - TableClassNamePeer::NUM_LAZY_LOAD_COLUMNS) + 1;
 
    TableClassNameI18nPeer::addSelectColumns($c);
 
    $c->addJoin(TableClassNamePeer::ID, TableClassNameI18nPeer::ID);
    $criterion = $c->getNewCriterion(TableClassNameI18nPeer::CULTURE, $culture);
    $criterion->addOr($c->getNewCriterion(TableClassNameI18nPeer::CULTURE, $default_culture));
    $c->add($criterion);
 
    $rs = BasePeer::doSelect($c, $con);
    $results = array();
    $uncultured_results = array();
 
    while($rs->next()) {
 
      $omClass = TableClassNamePeer::getOMClass();
 
      $cls = Propel::import($omClass);
      $obj1 = new $cls();
      $obj1->hydrate($rs);
      $obj1->setCulture($culture);
 
      if(isset($results[$obj1->getId()]))
      {
        $obj1 = $results[$obj1->getId()];
      }
 
      $omClass = TableClassNameI18nPeer::getOMClass($rs, $startcol);
 
      $cls = Propel::import($omClass);
      $obj2 = new $cls();
      $obj2->hydrate($rs, $startcol);
 
      $obj1->setTableClassNameI18nForCulture($obj2, $obj2->getCulture());
      $obj2->setTableClassName($obj1);
 
      if(!isset($uncultured_results[$obj1->getId()]))
      {
        $uncultured_results[$obj1->getId()] = $obj1;
      }
 
      if($obj2->getCulture() == $culture)
      {
        $uncultured_results[$obj1->getId()] = false;
      }
 
      if(!isset($results[$obj1->getId()]))
      {
        $results[$obj1->getId()] = $obj1;
      }
      elseif($obj2->getCulture() == $culture)
      {
        // Move result to the end of results array to fit eventual sort
        // criteria (ugly fix).
        unset($results[$obj1->getId()]);
        $results[$obj1->getId()] = $obj1;
      }
 
    }
 
    foreach($uncultured_results as $obj1)
    {
      if($obj1)
      {
        $obj1->setCulture($default_culture);
        $default_culture_object = $obj1->getCurrentTableClassNameI18n();
        if($default_culture_object)
        {
          $obj2 = new TableClassNameI18n();
          $default_culture_object->copyInto($obj2);
          $obj2->setCulture($culture);
          $obj2->setTableClassName($obj1);
          $obj1->setTableClassNameI18nForCulture($obj2, $obj2->getCulture());
        }
        $obj1->setCulture($culture);
      }
    }
 
    return array_values($results);
  }
 

Counting results filtered on translations

If you want to use a pager with a filter on translations, you will need this count method. Once this method present, the magic is done by :

    $criteria->setDistinct();
    $pager->setCriteria($criteria);
    $pager->setPeerMethod('doSelectWithI18n');
    $pager->setPeerCountMethod('doCountWithI18n');
 

Note : The setDistinct is very important for this snippet to work. It should not falsify your results, and without it, the doCountWithI18n method could return bad results.

To enable the fallback, edit your table object peer class in lib/model/TableClassNamePeer.php and add the following code. Then search and replace TableClassName by your table object class name.

  /**
   * Returns the number of rows matching criteria with I18N criteria.
   *
   * @param      Criteria $criteria
   * @param      boolean $distinct Whether to select only distinct columns (You can also set DISTINCT modifier in Criteria).
   * @param      Connection $con An optional database connection
   * @param      string $culture The selected culture.
   * @return     int Number of matching rows.
   */
  public static function doCountWithI18n(Criteria $criteria, $distinct = false, $con = null, $culture = null)
  {
    // we're going to modify criteria, so copy it first
    $criteria = clone $criteria;
 
    $default_culture = sfConfig::get('sf_i18n_default_culture');
 
    if ($culture === null)
    {
      // We use current user culture.
      $culture = sfContext::getInstance()->getUser()->getCulture();
    }
 
    // clear out anything that might confuse the ORDER BY clause
    $criteria->clearSelectColumns()->clearOrderByColumns();
    $criteria->addSelectColumn(TableClassNamePeer::COUNT_DISTINCT);
 
    // just in case we're grouping: add those columns to the select statement
    foreach($criteria->getGroupByColumns() as $column)
    {
      $criteria->addSelectColumn($column);
    }
 
    $criteria->addJoin(TableClassNamePeer::ID, TableClassNameI18nPeer::ID);
    $criterion = $criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $culture);
    $criterion->addOr($criteria->getNewCriterion(TableClassNameI18nPeer::CULTURE, $default_culture));
    $criteria->add($criterion);
 
    $rs = TableClassNamePeer::doSelectRS($criteria, $con);
    if ($rs->next()) {
      return $rs->getInt(1);
    } else {
      // no rows returned; we infer that means 0 matches.
      return 0;
    }
  }
 
by Pierre-Yves Landuré on 2007-10-10, tagged culture  data  database  i18n  object  propel 
(4 comments)

Browser culture detection

This filter use the accepted-languages browser setting to setup the user culture. The autodetection is run once by session.

This filter is sponsored by Dorigo consultants.

It check app.yml for accepted_languages setting in order to fit the available application locales.

It allow the use of sf_culture request parameter overide (this overide is avaible by default in symfony afaik). So you can use language changing links in your application.

It may be enhanced by a check of the really available locales but i don't know how it can be done.

To use this filter, create a file named SwitchLanguageFilter.class.php in lib/ or app/frontend/lib and fill it with this code and follow its documentation :

<?php
/**
 * Setup user culture from request
 * 
 * from http://www.symfony-project.com/snippets/snippet/80
 * Thanks to François Zaninotto
 * Thanks to Garfield-fr on #symfony-fr
 * 
 * Add the following lines in your app.yml to configure your application available languages.
 *   all:
 *     accepted:
 *       languages: [en, fr]
 * 
 * Then add this to your filters.yml
 * 
 *   # Filter that setup user culture
 *   mySwitchLanguageFilter:
 *     class: SwitchLanguageFilter
 * 
 * @package    SwitchLanguageFilter
 * @subpackage filter
 * @author     Pierre-Yves Landuré <py.landure@dorigo.fr>
 */
 
class SwitchLanguageFilter extends sfFilter
{
 
  /**
   * Check that the language is a valid application culture.
   * @param string $language The tested language code
   * @param string $default_language The default language code
   * 
   * @return string $language if no accepted languages is set,
   *                else $language if it is in accepted languages
   *                else $default_language
   */
  private function getAvailableCulture($language, $default_language = null)
  {
    $all_languages = sfConfig::get('app_accepted_languages', array());
 
    if(count($all_languages))
    {
      if(in_array($language, $all_languages)) // Test if language is available
      {
        return $language;
      }
      else // Else test if first part of language is available
      {
        $language_parts = explode('_', $language);
        if(count($language_parts))
        {
          if(in_array($language_parts[0], $all_languages))
          {
            return $language_parts[0];
          }
        }
      }
      return $default_language;
    }
 
    return $language;
  }
 
  /**
   * The filter call.
   */
  public function execute ($filterChain)
  {
    $context = $this->getContext();
    $user = $context->getUser();
 
    $default_culture = sfConfig::get('sf_i18n_default_culture');
    $selected_culture = $user->getCulture();
 
    if(!$user->getAttribute('sf_culture_autodetected', false))
    {
      $browser_languages = $context->getRequest()->getLanguages();
 
      foreach($browser_languages as $language)
      {
        $allowed_culture = $this->getAvailableCulture($language);
        if($allowed_culture)
        {
          $selected_culture = $allowed_culture;
          break;
        }
      }
 
      $user->setAttribute('sf_culture_autodetected', true);
    }
 
    $selected_culture = $context->getRequest()->getParameter('sf_culture', $selected_culture);
    $selected_culture = $this->getAvailableCulture($selected_culture, $default_culture);
    if($selected_culture != $user->getCulture())
    {
      // The user wants to see the page in another language
      $user->setCulture($selected_culture);
    }
 
    $filterChain->execute();
  }
}
 
by Pierre-Yves Landuré on 2007-10-09, tagged culture  filter  i18n  request 

format_number_choice in JavaScript

If you are dealing with i18n and client side effects, you might need an equivalent for the format_number_choice() helper in Javascript. Here is a simplified version:

function format_number_choice(text_string, replace_hash, number) {
  pattern = new RegExp("\\["+number+"\\]\s?([^\|]*)");
 
  function replace_in_string(text_string, replace_hash) {
    for(var i in replace_hash)
     text_string = text_string.replace(new RegExp(i), replace_hash[i]); 
    return text_string;
  }
 
  matches=text_string.match(pattern);
  if(matches != null)
    return replace_in_string(matches[1], replace_hash);
  else {
    pattern = /\[else\]\s?([^\|]*)/;
    matches=text_string.match(pattern);
    if(matches != null)
      return replace_in_string(matches[1], replace_hash);
    else
      return "not found";
  }
}

Use it in your HTML code as follows:

<script language="JavaScript" type="text/javascript">
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", {'Apple' : 'fruit'}, 0))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 1))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 2))
  document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 3))
</script>

This will output:

No fruit
One Apple
Two Apples
Many Apples

Now, just use enclose the first argument with the __() helper, and you have an internationalized format_number_choice() on the client side.

by Francois Zaninotto on 2006-08-29, tagged i18n  javascript 

How to have common parameters for every route

Let's take an example. You want to have the user culture in front of each of your URL.

So, your routing configuration look like something like that:

page:
  url: /:culture/:page
  params: ...

article:
  url: /:culture/:year/:month/:day/:slug
  params: ...

...

This is the easy part. But now, every time you call url_for() or link_to() you have to pass the culture parameter.

For such situations, there is a better way. The routing has a special configuration parameter named sf_routing_defaults that you can set with default values:

sfConfig::set('sf_routing_defaults', array('culture' => 'en'));

And now, the culture parameter will automatically added to the parameters that you pass to the url_for() or link_to() helpers.

by Fabien Potencier on 2006-08-23, tagged i18n  routing 
(8 comments)

call the translation helper from an action

You sometimes need to access the __() helper for text translation outside of a template. There are two ways to do it.

You can include the I18NHelper.php manually in your action, or use the symfony command to include a helper (sfLoaded::LoadHElpers()):

sfLoader::LoadHelpers(array('I18N'));

The problem is that this will add the helper functions to the global namespace. Alternately, use the following syntax:

sfContext::getInstance()->getI18n()->__($text, $args, 'messages');

Note: prior to 1.0, you had to do this (now deprecated)

sfConfig::get('sf_i18n_instance')->__($text, $args, 'messages');

(updated 29/11/06)

by Francois Zaninotto on 2006-07-24, tagged helper  i18n 
(3 comments)

Get a date from a culture dependent source

In an i18n application, if your template uses the input_date_tag() helper, the format of the date sent to the submit action will depend on the user culture. But then, how can you handle this date to, say, store it in a database in a culture independent format?

The answer lies in the sfI18N class:

$date= sfContext::getInstance()->getRequest()->getParameter('birth_date');
$user_culture = sfContext::getInstance()->getUser()->getCulture();
list($d, $m, $y) = sfI18N::getDateForCulture(
  $date,
  $user_culture
);

Now you have the day, month and year of the date in the $d, $m and $y variables, and you can do whatever you want with them.

If you use a Propel date setter, you can even call it directly with:

$person->setBirthDate("$y-$m-$d");

The same applies for the sfI18N::getTimestampForCulture() method.

by Francois Zaninotto on 2006-06-23, tagged culture  date  i18n 

Localized image Helper

Returns localized image <img> tag bassed on the the user culture and the image file location in the filesystem.

Based on the technique recomended in the symfony advent calendar day twenty-three

Directory tructure:

Inside images directory, create one directory for each culture in use plus one (named 'no-loc' by default) for non-localized images or images that doesn't change for different cultures.

Example:

web/images/en_US
web/images/es_MX
web/images/fr_FR
................
web/images/non-loc

<?php
/**
 * Returns localized image <img> tag bassed on the the user culture
 * and the image file location in the filesystem
 * 
 * If the image file is found under the directory corresponding
 * the user culture, it returns its img tag,
 * else returns img tag under 'non-loc'directory.
 * 
 *  Structure example:
 * 
 *   web/images/en_US
 *   web/images/es_MX
 *   web/images/fr_FR
 *   ................
 *   web/images/non-loc
 * 
 * $source   -  relative path within culture directory
 * $options  -  is a normal options array passed to image_tag function
 * 
 */
function local_image_tag($source, $options = array())
{
  $images_path       = sfConfig::get('sf_root_dir') . '/web/images';
  $non_localized_dir = 'non-loc';
  $culture           = sfContext::getInstance()->getUser()->getCulture();
  $default_ext       = 'png';
 
  if (strpos(basename($source), '.') === false)
  {
    $source .= '.'.$default_ext;
  }
 
  if (!$culture || !(is_file($images_path.'/'.$culture.'/'.$source)))
  {
    return image_tag($non_localized_dir.'/'.$source, $options);
  }
 
  return image_tag($culture.'/'.$source, $options);
}
?>

Call example:

<?php
echo local_image_tag('some/image', 'alt=image');
/*
Will search in /web/images/<culture>/some/image.png,
and if file is not found, will return image tag in
/web/images/non-loc/some/image.png
*/
?>
by David Regla on 2006-05-25, tagged helper  i18n 
(1 comment)