If you use Symfony and ever tried to add language support through i18n you might have encountered the same problem as I did. It seems easy at first to add language support: the only thing you need to do is add an extra underline (_) compared to the _() gettext function I would normally use to add language support. This works perfectly in the view part of the application and you don’t even need sprintf anymore, because the __() function from symfony has similar stuff built right into it.

But then you’d like to add some user flash messages generated in your myUser class, or even worse: make sure your forms are translated! I spent some time trying to figure out the best approach and came up with some interesting 

MVC Pattern

As you probably know Symfony uses the MVC (Model-View-Controller) Pattern heavily. This is mostly a good thing, and if you consider it a pain-in-the-ass you should probably not consider yourself a proper OOP programmer, nor use Symfony. That said there are a view exceptions when you need to put your translations in the controller. We can hardly write a big switch for each point you got errors, now can we?

The solution is pretty simple.  You just need to add the following code to make the __() function accessible wherever you need it.
<?php
$context = sfContext::getInstance(); // In Actions you can use $this->getContext(); instead
$i18n = $contect->getI18n();
echo $i18n->__("I'm a translated string");
?>

This seems great (even though it’s bloated), but there’s two things I don’t like about this:

  1. Even the documentation warns you that getContext loads a lot of overhead and shouldn’t be used extensively. But if you’re going to use it for translating you’re doing exactly that: using it extensively.
  2. The next thing is that there’s this wonderful little task: i18n:extract that you basically NEED to extract all your strings.. especially if you’re application grows and doing it by hand is just not an option. The downside? It doesn’t extract anything else than the Action class and the view..

Extracting Forms

I already talked about i18n:extract task and the necessity of it.. but it also doesn’t support Forms. The documentation states it as follows:

Unfortunately, the i18n:extract task does not yet parse form classes for untranslated strings.

I wasn’t about to take no for an answer, so I was already opening my Netbeans to create my own form extractor – how hard could it be? Turns out that if you want to create your own code to do this you have to delve deeper into plugins and the way Symfony is programmed. In this case I lacked the time to go this far, but had already discovered a great keyword to use on Google: sfI18nFormExtractor (since that’s how the other extractors are called). Google quickly lead me to find the sfI18nFormExtractorPlugin. This nifty little plugin is hardly used by anyone, but adds a task i18n:extract-forms that basically works the same as standard extract task that successfully extracts your form strings.

Combining error messages and Forms

There’s more?! Yep! Knowing I could use the Forms to add i18n messages without even adding the __() function I invented a new approach to my error messages. I made a real Form of my previously hacked-in very basic login form and added custom messages for the “required” fields.
$this->getValidator('login')->setMessage('required', "Please fill in your user name.");
$this->getValidator('password')->setMessage('required', "Don't forget your password!");
?>

The last challange was how to validate the ‘incorrect username and/or password’. I could’ve added a DoctrineValidation, but as that would add another database call I refused. I moved the code to myUser and made a login() function that basically handled the form.

public function login(UserLoginForm $form, sfWebRequest $request)
{
// get the values through the form
$login = $form->getValue('login');
$password = $form->getValue('password');

// if user forgets either login or password or both I just want them to receive one flash error, which is the first of the errors.
if (!$form->isValid())
{
$error = current($form->getErrorSchema()->getErrors());
$this->setFlash('error', $error );
return false;
}

$secret_password = md5(sfConfig::get('app_password_secret').$password);

// data valid, check with db (this will later be moved to User.class.php)
$query = Doctrine_Query::create()->from('User')
->where('login = ? AND password = ?', array($login, $secret_password))
->execute();

// of course this should return a user.. if it doesn't we need an error
if ($query->count() == 0)
{
// notice here I use a custom validation message set by
// $form->getValidatorSchema()->addMessage('notfound', 'Incorrect username/password')
$this->setFlash('error', $form->getValidatorSchema()->getMessage('notfound'));
return false;
}

// get instance of User
$user_obj = $query->getFirst(); // as login is unique this can never have other results

// set credentials, authentication and attributes
$this->setAuthenticated(true);
// ...
return true;
}
?>

This way I moved my I18n problem to a custom validation message in the form ‘notfound’ and there it gets translated and extracted as I would expect with help of my plugin. In the end the code is pretty clean and the error messages are where I would expected them (bundled together in UserLoginForm.class.php)

Feel free to point out any mistake :)
Learn more about Forms and I18n through the Symfony documentation

Symfony Tips & Tricks

  1. Symfony I18n and messages extraction
  2. Use gmail with swift mailer
  3. Generalizing and formatting forms