Translate Symfony TimezoneType Field Type

The TimezoneType Field Type

Symfony has an specific Field Type to create a Timezone select widget in forms called TimezoneType. Very handy because it’s a pretty usual need in web applications.

select

It uses \DateTimeZone::listIdentifiers() to create the texts and as you can see those texts are in english:

    public static function getTimezones()
    {
        if (null === static::$timezones) {
            static::$timezones = array();

            foreach (\DateTimeZone::listIdentifiers() as $timezone) {
                $parts = explode('/', $timezone);

                if (count($parts) > 2) {
                    $region = $parts[0];
                    $name = $parts[1].' - '.$parts[2];
                } elseif (count($parts) > 1) {
                    $region = $parts[0];
                    $name = $parts[1];
                } else {
                    $region = 'Other';
                    $name = $parts[0];
                }

                static::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
            }
        }

        return static::$timezones;
    }

¿But what if you want to translate all those texts to your language?

Note: I’m going to assume that you are storing the users timezone in the database as it’s defined in PHP. For example “Europe/Madrid” for Spain (without the Canary Islands) or “America/North_Dakota/New_Salem” for the Morton County in USA. This is the timezone we are going to use as an example.

As you can see the timezones are divided in different parts using the “/” character. The first one designates an area (more or less a continent but not exactly) and then a city. In some cases it has 3 parts for a more specific area. We also have some special ones like UTC for the Universal Time Clock.

We are going to start by creating our form with the TimezoneType for the user profile edit page:

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;

class ProfileFormType extends AbstractType
{
    private $class;

    public function __construct($class)
    {
        $this->class = $class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $constraint = new UserPassword();

        $this->buildUserForm($builder, $options);

        $builder
            ->add('timezone', 'timezone', array(
                'label' => 'form.timezone',
                'translation_domain' => 'AppBundle',
                'choice_translation_domain' => true,
            ))
            ->add('current_password', 'password', array(
                'label' => 'form.current_password',
                'translation_domain' => 'FOSUserBundle',
                'mapped' => false,
                'constraints' => $constraint,
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => $this->class,
            'intention'  => 'profile',
        ));
    }

    public function getName()
    {
        return 'app_user_profile_form_type';
    }

    protected function buildUserForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', 'email', array(
                'label' => 'form.email',
                'translation_domain' => 'FOSUserBundle',
            ))
        ;
    }
}

It’s VERY important to use the translation_domain and choice_translation_domain options as seen above or this won’t work.

Then we have to create the translation files. Assuming you are following the Symfony Best Practices you have one bundle called AppBundle and its translation files, in YAML format, are in app/Resources/translations. The file name uses the locale code to designate the language, for example for spanish it would be AppBundle.es.yml. You can download a list of all the texts for english and spanish in those AppBundle.en.yml and AppBundle.es.yml files so you can use it as a template. This are some of those texts translated to spanish with our example in bold:

## app/Resources/translations/AppBundle.es.yml
# Timezone areas
Africa: "África"
America: "América"
Antarctica: "Antártida"
Arctic: "Ártico"
Asia: "Asia"
Atlantic: "Atlántico"
Australia: "Australia"
Europe: "Europa"
Indian: "India"
Pacific: "Pacífico"
# Specific timezones
Abidjan: "Abidjan"
Accra: "Accra"
Addis Ababa: "Addis Ababa"
Algiers: "Algiers"
# ... more and more timezones here ...
North Dakota - New Salem: "Dakota del Norte - New Salem"
# ... more and more timezones here ...
UTC: "UTC"

All we have to do now to see our TimezoneType Field Type translated to spanish is clear the cache ($ php app/console cache:clear --env=dev) and open our form page using the spanish locale. To force our application the spanish locale, if you aren’t already using multilanguage in your app, you can edit app/config/config.yml:

framework:
    translator:      { fallbacks: ["es"] }
    default_locale:  "es"

This is how my translated widget looks like:

translate_timezonetype

But we also want to translate the timezone stored in the database when showing it in the users profile page. Remember that we store the users timezone in its original PHP timezone string: “America/North_Dakota/New_Salem“. If we want to reuse all the translations we did before we can use this approach in Twig:

{{ (user.timezone|split("/")|first)|trans({}, 'AppBundle') }}

{{ (user.timezone|split("/", 2)|last|replace({'_': ' '})|replace({'/': ' - '}))|trans({}, 'AppBundle') }}

The first line will translate the timezone area (Africa, America, etc.) by spliting the timezone using the “/” character and then getting the first element. Becareful and remember using the opening and closing parenthesis before “|trans()” or this won’t work.

The second line is a little bit tricky. It splits the timezone string using the “/” character but limits the amount of parts to 2 and then gets the last item so we can discard the timezone area. If we take as an example “America/North_Dakota/New_Salem” we’ll have “North_Dakota/New_Salem” in this first step. Now we replace “_” with spaces and “/” with “ - “. This will convert “North_Dakota/New_Salem” to “North Dakota - New Salem” and if you look up there in the translation written in bold that’s exactly the key for the translation.

translated_timezone_text

¡HEY! ¡Don’t forget to clear your cache everytime you change or add a translation! They are not created on every request.

$ php app/console cache:clear --env=dev

You might also like

Symfony Forms and Bootstrap Datetimepicker
In this example I'm going to use the spanish locale and the "Europe/Madrid" timezone.First we need...

Using dbDelta with WordPress to create and alter tables
dbDelta is used in WordPress to create and update tables in the database and you will usually use it...

Multiple class inheritance with Doctrine ODM in Symfony: One collection for multiple document types
When you use Doctrine ORM in Symfony all the data is stored in tables having every row the same columns....

Fix “This field was locked by vendor” in Plesk Updates source and installation settings
I was trying to install some new components to one on my Plesk managed servers but I got this error when...

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.