The t()
function
Introduction
When talking about localization, t()
is the main function that 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>
Concrete CMS will automatically translate Cancel
to the current user language. Great, isn't it?
Parameters
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 is wrong, since the translatable string must be a constant.
You may also think to write echo t('Edit').' '.$blockTypeName;
. This approach too should be avoided, since it supposes that in every language the translation of 'Edit' comes before the block type name (for instance, in German they are reversed).
So, the correct way to write it is 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 as:
echo t('Edit %s', $blockTypeName);
What if we've more than one variable 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 need to enable translatation for this:
echo 'Posted by '.$user.' on '.$date;
Instead of writing echo t('Posted by %s on %s', $user, $date);
you should write:
echo t('Posted by %1$s on %2$s', $user, $date);
Using numbered arguments will allow translators to switch the arguments order to allow more correct translations.
PS: To know the format of the variable placeholders see the description of the sprintf parameters in the PHP manual.
Comments
Especially when using more parameters, it's often useful to communicate to the translators the meaning of the various variable 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 date/time*/'Posted by %1$s at %2$s', $user, $date);
Those parameters will be visible to translators, giving them a great help.
The t2()
function
You may want to translate strings that are number-dependent, and that require different translations for singular and plural forms. 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 forms in all 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, 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 in this case).
Furthermore, a call to sprintf
is called, so that $pages
will be inserted in the resulting string.
The tc()
function
Sometimes, an English text may have multiple meanings, so it may have multiple 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()
, but its first parameter is a string describing the context. For instance, we may have:
echo tc('A group of items', 'Set');
and also
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 they will be visible to translators, helping them to better understand 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 translators and it 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 Ok
) 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.
Core contexts
In order to avoid translations problems, Concrete implements with the following core contexts:
AreaName
area namesAttributeKeyName
names of the attribute keysAttributeSetName
names of the attribute setsAttributeTypeName
names of the attribute typesBlockTypeSetName
names of the block setsConversationEditorName
names of the editorsConversationRatingTypeName
names of the rating types of the conversationsFeedDescription
descriptions of the feedsFeedTitle
titles of the feedsGatheringDataSourceName
names of the gathering data sourcesGatheringItemTemplateName
names of the gathering item templatesGroupDescription
descriptions of the user groupsGroupName
names of the user groupsImageEditorControlSetName
names of the control sets of the image editorImageEditorFilterName
names of the filters of the image editorJobSetName
names of the job setsPageTemplateName
names of the page templatesPageTypeComposerControlName
names of the controls of the page type composersPageTypeComposerControlTypeName
names of the types controls of the page type composersPageTypeComposerFormLayoutSetControlCustomLabel
names of the set of layouts of the page type composersPageTypeComposerFormLayoutSetName
names of the sets of page type composer formsPageTypePublishTargetTypeName
names of the targets of page typesPermissionAccessEntityTypeName
names of permission access entity typesPermissionKeyDescription
descriptions of permission keysPermissionKeyName
names of permission keysPresetName
names of style presetsSelectAttributeValue
values of the select attribute typesStyleName
names of the stylesStyleSetName
names of the set of stylesSystemContentEditorSnippetName
names of the snippets of the content editorTemplateFileName
names associated to template filesThumbnailTypeName
names of the types of the image thumbnailsTopic
topicsTopicCategoryName
names of the topic categoriesTopicName
topic namesTreeName
names of the trees
Dates and times
In php, if you want the English name of the current month, you write this:
echo date('F');
But how can you retrieve the name of the month in the current site locale?
In Concrete there's the Date
helper, that, as its name says, helps you with dates. It's very simple to use:
$dateHelper = \Core::make('helper/date');
In order to get the localized month name, you simply use its date
method instead of the PHP built-in date
function:
echo $dateHelper->date('F');
That's it. Please note that this date
method accepts the same format string used by the standard php date() function.
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 something like echo $dateHelper->date('F d, Y');
, but this 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
). This is wrong.
For a correct date and/or time localization you should use the following ready-to-use methods of the Date helper:
$dateHelper->formatDate()
to format a date only (reference)$dateHelper->formatTime()
to format a time only (reference)$dateHelper->formatDateTime()
to format a date and time (reference)
The date helper offers also a lot of other useful date-related functions: take a look at the API docs.
Translating packages
So, you've developed your own package. Great! You did everything possible to make it a thing that will change the world. And to let the whole world use it, you enabled localization by writing all those t()
functions.
And now? How could your ready-to-be-translated package speak other languages?
You need to extract the translatable strings from your PHP files and create one language file for every language you plan to translate.
Starting from version 5.7.5.4, you can use Concrete itself to do it:
open a console window and type the following command (replace the /
with \
if you use Windows):
PATH-TO-CONCRETE5-ROOT-FOLDER/concrete/bin/concrete5 c5:package-translate PACKAGE_HANDLE OPTIONS
where:
PATH-TO-CONCRETE5-ROOT-FOLDER
is the path of your Concrete installation directoryPACKAGE_HANDLE
is the handle of your package (or the path to the directory that contains it)OPTIONS
use--help
for a list of all the available options. The most important one is--locale
(or its short form-l
) that allows you to specify which language files you want to create.
For instance, let's assume you developed a package identified by the handyman
handle.
To extract the translations and create the language files for German and Italian simply write:
concrete5 c5:package-translate handyman -l it_IT -l de_DE
This command actually creates these files:
packages/handyman/languages/messages.pot
this is the so-called gettext Portable Object Template file: it contains all the translatable strings found in your package; it could be used to create translation files for new languages, but sincec5:package-translate
is smart enough to create the language files for you, you can ignore it.packages/handyman/languages/it_IT/LC_MESSAGES/messages.po
this is the so-called Portable Object file that should be translated into Italian.packages/handyman/languages/it_IT/LC_MESSAGES/messages.mo
this is the so-called Machine Object file and it's the compiled version of the.po
file.packages/handyman/languages/de_DE/LC_MESSAGES/messages.po
same as the file underit_IT
but for Germanpackages/handyman/languages/de_DE/LC_MESSAGES/messages.mo
same as the file underit_IT
but for German
So, what should be done now?
Translators have to translate the .po
files. They can use with a normal text editor that supports UTF-8, but it's much easier to use one of the the many programs available for this purpose (for instance, POEdit, BetterPOEditor, ...)
Concrete can't use these .po
files directly, they need to be compiled to the .mo
format.
In order to perform this compilation, you simply call the c5:package-translate
command-line command again (only with the package handle):
concrete5 c5:package-translate handyman
This command will see that you have the .po
files for Italian and German, and it will compile them to the .mo
format (you can also use another command-line command: c5:package-pack
).
After compiling the .po
files, in order to see the new translations in your websites you have to clear the Concrete cache (via the Concrete dashboard or with the c5:clear-cache
command line command).
PS: when you distribute your package, you can omit to include the .pot
and .po
files, since Concrete will only need the .mo
files. The c5:package-pack
can do this for you.