Caution: You are browsing the legacy 1.x part of this website.
This version of symfony is not maintained anymore. If some of your projects still use this version, consider upgrading.

Master Symfony fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
training.sensiolabs.com

Discover SensioLabs' Professional Business Solutions

Peruse our complete Symfony & PHP solutions catalog for your web development needs.
sensiolabs.com

A lot of popular Web applications are available in several languages, and sometimes, they are even customized based on the user culture. Symfony comes with a built-in framework that eases the management of these features (see chapter "I18n And L10n" (/book/1_2/13-I18n-and-L10n) of the symfony book).

The form framework also comes with built-in support for the user interface translation and provides an easy way to manage internationalized objects.

Form Internationalization

A symfony form is internationalizable by default. The translation of the labels, the help texts, and the errors messages can be done by editing the translation files, be they in the XLIFF, gettext, or any other symfony supported format.

Listing 8-1 shows the contact form we have developed in the previous chapters.

Listing 8-1 - Contact Form

class ContactForm extends BaseForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'  => new sfWidgetFormInputText(), // the default label is "Name"
      'email' => new sfWidgetFormInputText(), // the default label is "Email"
      'body'  => new sfWidgetFormTextarea(),  // the default label is "Body"
    ));
 
    // Change the email widget label
    $this->widgetSchema->setLabel('email', 'Email address');
  }
}

We can now define the label translations in the XLIFF file as shown in Listing 8-2 for the french language.

Listing 8-2 - XLIFF translation file

// apps/frontend/i18n/messages.fr.xml
<?xml version="1.0" ?>
<xliff version="1.0">
  <file original="global" source-language="en" datatype="plaintext">
    <body>
      <trans-unit>
        <source>Name</source>
        <target>Nom</target>
      </trans-unit>
      <trans-unit>
        <source>Email address</source>
        <target>Adresse email</target>
      </trans-unit>
      <trans-unit>
        <source>Body</source>
        <target>Message</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Specify the catalogue to use for translations

If you use the catalogue feature of the symfony i18n framework (/book/1_2/13-I18n-and-L10n#chapter_13_sub_managing_dictionaries), you can bind a form to a given catalogue. In Listing 8-3, we associate the ContactForm form with the contact_form catalogue. So, the form element translations will be looked for in the contact_form.fr.xml file.

Listing 8-3 - Translation Catalogue Customization

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
 
    $this->widgetSchema->getFormFormatter()->setTranslationCatalogue('contact_form');
  }
}

note

The usage of catalogues allows a better organization of your translations by using one file per form for example.

Error Messages Internationalization

Sometimes, the error messages embed the value submitted by the user (for example, "The email address user@domain is not valid."). We have already seen in Chapter 2 that this can be done easily in the form class by defining customized error messages and using references to the user submitted values. These references follow the %parameter_name% pattern.

The Listing 8-4 shows how to apply this principle to the name field of the contact form.

Listing 8-4 - Error Messages Internationalization

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
 
    $this->validatorSchema['name'] = new sfValidatorEmail(
      array('min_length' => 2, 'max_length' => 45),
      array('min_length' => 'Name "%value%" must be at least %min_length% characters.',
            'max_length' => 'Name "%value%" must not exceed %max_length% characters.',
      ),
    );
  }
}

We can now translate these error messages by editing the XLIFF file as shown in Listing 8-5.

Listing 8-5 - XLIFF Translation File for Error Messages

<trans-unit>
  <source>Name "%value%" must be at least %min_length% characters</source>
  <target>Le nom "%value%" doit comporter un minimum de %min_length% caractères</target>
</trans-unit>
<trans-unit>
  <source>Name "%value%" must not exceed %max_length% characters</source>
  <target>Le nom "%value%" ne peut comporter plus de %max_length% caractères</target>
</trans-unit>

Customization of the Translation object

If you want to use the symfony form framework without the symfony i18n framework, you need to provide your own translation object.

A translation object is just a callable PHP. It can be one of the following three things:

  • a string representing a function name, like my_function

  • an array with a reference to a class instance and the name of one of its methods, like array($anObject, 'oneOfItsMethodsName')

  • a sfCallable instance. This class encapsulate a PHP callable in a consistent way.

note

A PHP callable is a reference to a function or a method instance. It is also a PHP variable that returns true when passed to the is_callable() function.

Let's take an example. You have to migrate a project which already has its own internationalization mechanism provided by the class show in Listing 8-6.

Listing 8-6 - Custom I18N class

class myI18n
{
  static protected $default_culture = 'en';
  static protected $messages = array('fr' => array(
    'Name'    => 'Nom',
    'Email'   => 'Courrier électronique',
    'Subject' => 'Sujet',
    'Body'    => 'Message',
  )); 
 
  static public function translateText($text)
  {
    $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; 
    if (array_key_exists($culture, self::$messages)
        && array_key_exists($text, self::$messages[$culture]))
    {
      return self::$messages[$_SESSION['culture']][$text];
    }
    return $text;
  }
}
 
// Class usage
$myI18n = new myI18n();
 
$_SESSION['culture'] = 'en';
echo $myI18n->translateText('Subject'); // => display "Subject"
 
$_SESSION['culture'] = 'fr';
echo $myI18n->translateText('Subject'); // => display "Sujet"

Each form can define its very own callable which will manage the internationalization of the form elements as shown in Listing 8-7.

Listing 8-7 - Overriding of the Internationalization Method for a Form

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
    $this->widgetSchema->getFormFormatter()->setTranslationCallable(array(new myI18n(), 'translateText'));
  }
}

Translation Callable Accepted Parameters

The translation callable can take up to three arguments :

  • the text to translate;

  • an associative array of arguments to replace within the original text, typically to replace dynamic arguments as we have seen previously in this chapter;

  • a catalogue name to use when translating the text.

Here is the call used by the sfFormWidgetSchemaFormatter::translate() method to call the translation callable:

return call_user_func(self::$translationCallable, $subject, $parameters, $catalogue);

The self::$translationCallable is the reference to the translation callable. So, the previous code is equivalent to:

$myI18n->translateText($subject, $parameters, $catalogue);

Here is the updated version of the MyI18n class that supports those extra arguments:

class myI18n
{
  static protected $default_culture = 'en';
  static protected $messages = array('fr' => array(
    'messages' => array(
      'Name'    => 'Nom',
      'Email'   => 'Courrier électronique',
      'Subject' => 'Sujet',
      'Body'    => 'Message',
    ),
  ));
 
  static public function translateText($text, $arguments = array(), $catalogue = 'messages')
  {
    $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; 
    if (array_key_exists($culture, self::$messages) &&
        array_key_exists($messages, self::$messages[$culture] &&
        array_key_exists($text, self::$messages[$culture][$messages]))
    {   
      $text = self::$messages[$_SESSION['culture']][$messages][$text];
      $text = strtr($text, $arguments);
    }   
    return $text;
  }
}

sidebar

Why do we use the sfWidgetFormSchemaFormatter to customize the Translation Process?

As we have seen in Chapter 2, the form framework is based on the MVC architecture and the sfWidgetFormSchemaFormatter class belongs to the View layer. This class is responsible for all the text rendering, so it can intercept all the text strings and translate them on the fly.

Propel Objects Internationalization

The form framework has built-in support for Propel objects that are internationalized. Let's take an internationalized model example to illustrate the way it works:

propel:
  article:
    id:
    author:     varchar(255)
    created_at:
  article_i18n:
    title:      varchar(255)
    content:    longvarchar

You can generate the Propel classes and the related form classes with the following commands:

$ php symfony build:model
$ php symfony build:forms

Those commands generate some files in your symfony project:

lib/
  form/
    ArticleForm.class.php
    ArticleI18nForm.class.php
    BaseFormPropel.class.php
  model/
    Article.php
    ArticlePeer.php
    ArticleI18n.php
    ArticleI18nPeer.php

Listing 8-8 shows how to configure the ArticleForm to be able to edit the French and the English version of the article in the same form.

Listing 8-8 - I18n forms for an internationalized Propel Object

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->embedI18n(array('en', 'fr'));
  }
}

You can also customize the language labels of the form by adding the following code to the configure() method as shown in Listing 8-9.

Listing 8-9 - Language Labels Customizations

$this->widgetSchema->setLabel('en', 'English');
$this->widgetSchema->setLabel('fr', 'French');

Figure 8-1 - Internationalized Propel Form

Internationalized Propel Form

That's all there is to it. When you call the save() method of the form object, the associated Propel object and all the i18n objects are saved automatically.

sidebar

How to pass the User Culture to a Form?

If you want to bind a form to the current culture of the user, you can pass an optional culture option when you create the form:

class articleActions extends sfActions
{
  public function executeCreate($request)
  {
    $this->form = new ArticleForm(null, array('culture' => $this->getUser()->getCulture()));
 
    if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('article')))
    {
      $this->redirect('article/created');
    }
  }
}

In the ArticleForm class, you can now get the value from the options array:

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->embedI18n(array($this->getCurrentCulture()));
  }
 
  public function getCurrentCulture()
  {
    return isset($this->options['culture']) ? $this->options['culture'] : 'en';
  }
}

Localized Widgets

The symfony form framework is bundled with some widgets that are i18n "aware". They can be used to localize some widgets according to the user culture.

Dates selectors

Here are the available widgets to localize a date:

  • The sfWidgetFormI18nDate widget displays inputs for a date (day, month, year):

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nDate(array('culture' => 'fr'));

    You can also define the display format of the month, thanks to the month_format option which can take three different values:

    • name to display the name of the month (the default)
    • short_name to display the abbreviated name of the month
    • number to display the number of the month (from 1 to 12)
  • The sfWidgetFormI18nTime widget displays input for a time (hours, minutes, and seconds):

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nTime(array('culture' => 'fr'));
  • The sfWidgetFormI18nDateTime widget displays inputs for a date and a time:

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nDateTime(array('culture' => 'fr'));

Country selector

The sfWidgetFormI18nChoiceCountry widget displays a select box filled with a list of countries. The country names are translated in the given language:

$this->widgetSchema['country'] = new sfWidgetFormI18nChoiceCountry(array('culture' => 'fr'));

You can also restrict the countries in the select box, thanks to the countries option:

$countries = array('FR', 'EN', 'ES', 'DE', 'NL');
$this->widgetSchema['country'] = new sfWidgetFormI18nChoiceCountry(array('culture'   => 'fr',
                                                                         'countries' => $countries));

Culture selector

The sfWidgetFormI18nChoiceLanguage widget displays a select box filled with a list of languages. The language names are translated in the given language:

$this->widgetSchema['language'] = new sfWidgetFormI18nChoiceLanguage(array('culture' => 'fr'));

You can also restrict the languages in the select box, thanks to the languages option:

$languages = array('fr', 'en', 'es', 'de', 'nl');
$this->widgetSchema['language'] = new sfWidgetFormI18nChoiceLanguage(array('culture'   => 'fr',
                                                                           'languages' => $languages));

Timezone selector

The sfWidgetFormI18nChoiceTimezone widget displays a select box filled with a list of timezones.

$this->widgetSchema['timezone'] = new sfWidgetFormI18nChoiceTimezone();

In the news

The Symfony Certification by SensioLabs

Symfony 3 Certification now available in 4,000 centers around the world!

Get certified

Upcoming training sessions