This how-to is mainly targeted to developers that are willing to internationalize the code they're writing.
The t() function
t() is the main function that localization developers should be aware of.
It accepts one or more parameters. The first parameter is the string that should be translated. So, for instance, for a Cancel button you should write:
<button><?php echo t('Cancel'); ?></button>
concrete5 will automatically translate Cancel to the current site language. Great, isn't it?
Often, you have to insert a dynamic content into a translatable string. Let's do an example: if $blockTypeName
contains a block type name and you want to translate the following code:
echo 'Edit '.$blockTypeName;
you may think to write it as echo t('Edit').' '.$blockTypeName;
. This should be avoided, since that supposes that in every language the translation of 'Edit' comes before the block type name (for instance, in German they are reversed). You should write
echo sprintf(t('Edit %s'), $blockTypeName);
t() is smart enough to simplify your life, integrating the sprintf() php function; so you can write the above line this way:
echo t('Edit %s', $blockTypeName);
To know the format of the variable placeholders see the description of the sprintf() parameters in the PHP manual.
And what if we've more than one thing to insert into the string to be translated? In this case too we have to be quite careful, since words order can be different from one language to another.
For instance, if you want to translate Posted by John at 7.00 you could write:
echo t('Posted by %1$s at %2$s', 'John', '7.00');
Using numbered arguments will allow translators to switch the arguments order to allow more correct translations.
Comments
Especially when using more parameters, it's often useful to tell to the translators the meaning of the various variables placeholders (the %s).
You can do this with a simple php comment starting with i18n, like in this example:
echo t(/*i18n %1$s is a user name, %2$s is a time*/'Posted by %1$s at %2$s', 'John', '7.00');
Those parameters will be visible to translators, giving them a great help.
The tc() function (available since concrete5 5.6.1)
Sometimes, an English text may have various translations. Let's think about the word 'set': it has thousands of meanings, and so it may have thousands of translations. Which is the correct one? It depends on the context where this word is used. It is possible to specify the string context by using the tc() function: it's very similar to t(), except that its first parameter is a string describing the context. For instance, we may have:
echo tc('A group of items', 'Set');
echo tc('Apply a value to an option', 'Set');
Using contexts allows having different translations for the same English text. Please remark that the strings 'A group of items' and 'Apply a value to an option' won't be included in the translation, they are used only to have different translations for the same word 'Set', and will be visible to translators helping them to understand better the English text.
You may have some doubts about using or not contexts (that is, t() vs tc()). Using too many tc() will unnecessary increase the translation work, using too few tc() will cause problems to the translators and may lead to senseless translated web pages.
As a general rule, translatable texts that are quite long (e.g. composed by three or more words) shouldn't require contexts, since their context may be self-defined and their meaning can't be misunderstood. For short texts... it depends. If a word may have various meanings (like 'set') you should use contexts (so tc() instead of t()); if a word has just one meaning (like 'computer') you shouldn't use contexts (so t() instead of tc()).
Either way, listen carefully to translators' feedback: it's the only way to do things in the right way.
The t2() function (available since concrete5 5.6.0)
You may want to translate strings that are number-dependent, and that require a different translation for singular and plural. One common mistake is to do a simple php if-then-else, like this:
echo ($pages == 1) ? t('%d page', $pages) : t('%d pages', $pages);
This supposes that the plural form in the other languages are only two, and that the singular form is when $page is one and the plural form is when $page is not one. This assumption is wrong.
For instance, Russian has three plural forms, and in French zero is singular (we have zéro page and not zéro pages).
So, when you want to translate plural forms, don't use t(): there's its brother t2(). It takes at least three parameters (but often you'll use at least four), as in this example:
echo t2('%d page', '%d pages', $pages);
The t2() function returns the correct plural forms associated to the number stored in the third parameter (the $pages variable). It does just that, it does not put the variable into the translated string.
So, if $page is 2 you'll receive the translation of the text valid for that numeral value, but still have the '%d' in the string.
To insert the number of pages you have to write:
echo t2('%d page', '%d pages', $pages, $pages);
since the parameters after the third are used by the sprintf() function inside t2().
Date/time internationalization (available since concrete5 5.6.0)
If in php to want the name of the current month, you write
echo date('F');
This php codes returns the English name of the current month. But how can you retrieve the name of the month in the current site locale?
In concrete5 there's the Date helper, that, as its name says, helps you with dates. It's very simple to use:
$dateHelper = Loader::helper('date');
echo $dateHelper->date('F');
That's it. Simply retrieve an instance of the helper, and use its date method exactly like you do with the standard php date() function (it accepts the same parameters - see the PHP manual).
Furthermore, you may want to format a date. To write the current date in a long format In English you can call
echo date('F d, Y');
You could think to write
echo Loader::helper('date')->date('F d, Y');
This is wrong, since it assumes that in every language a date is written as the month name ('F') followed by the day in the month ('d'), a comma and the year ('Y'). That's not true.
For a correct date and/or time localization you should use the following ready-to-use constants that concrete5 offers you.
The samples are build with the line of code Loader::helper('date')->date(ConstantName, mktime(16, 30, 59, 12, 31, 2013);
DATE_APP_GENERIC_MDYT_FULL
- English value:F d, Y \a\t g:i A
- English example:December 31, 2013 at 4:30 PM
DATE_APP_GENERIC_MDYT_FULL_SECONDS
- English value:F d, Y \a\t g:i:s A
- English example:December 31, 2013 at 4:30:29 PM
(available since concrete5 5.6.2)DATE_APP_GENERIC_MDYT
- English value:n/j/Y \a\t g:i A
- English example:12/31/2013 at 4:30 PM
DATE_APP_GENERIC_MDY
- English value:n/j/Y
- English example:12/31/2013
DATE_APP_GENERIC_MDY_FULL
- English value:F j, Y
- English example:December 31, 2013
DATE_APP_GENERIC_T
- English value:g:i A
- English example:4:30 PM
DATE_APP_GENERIC_TS
- English value:g:i:s A
- English example:4:30:59 PM
Generate the language files and see t() at work
This final step to localize your code is covered by the Translate your package how-to.