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?
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.
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.
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
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);
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.
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');
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,
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
Either way, listen carefully to translators' feedback: it's the only way to do things in the right way.
In order to avoid translations problems, Concrete implements with the following core contexts:
AttributeKeyNamenames of the attribute keys
AttributeSetNamenames of the attribute sets
AttributeTypeNamenames of the attribute types
BlockTypeSetNamenames of the block sets
ConversationEditorNamenames of the editors
ConversationRatingTypeNamenames of the rating types of the conversations
FeedDescriptiondescriptions of the feeds
FeedTitletitles of the feeds
GatheringDataSourceNamenames of the gathering data sources
GatheringItemTemplateNamenames of the gathering item templates
GroupDescriptiondescriptions of the user groups
GroupNamenames of the user groups
ImageEditorControlSetNamenames of the control sets of the image editor
ImageEditorFilterNamenames of the filters of the image editor
JobSetNamenames of the job sets
PageTemplateNamenames of the page templates
PageTypeComposerControlNamenames of the controls of the page type composers
PageTypeComposerControlTypeNamenames of the types controls of the page type composers
PageTypeComposerFormLayoutSetControlCustomLabelnames of the set of layouts of the page type composers
PageTypeComposerFormLayoutSetNamenames of the sets of page type composer forms
PageTypePublishTargetTypeNamenames of the targets of page types
PermissionAccessEntityTypeNamenames of permission access entity types
PermissionKeyDescriptiondescriptions of permission keys
PermissionKeyNamenames of permission keys
PresetNamenames of style presets
SelectAttributeValuevalues of the select attribute types
StyleNamenames of the styles
StyleSetNamenames of the set of styles
SystemContentEditorSnippetNamenames of the snippets of the content editor
TemplateFileNamenames associated to template files
ThumbnailTypeNamenames of the types of the image thumbnails
TopicCategoryNamenames of the topic categories
TreeNamenames of the trees
Dates and times
In php, if you want the English name of the current month, you write this:
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
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.
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
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 18.104.22.168, you can use Concrete itself to do it:
open a console window and type the following command (replace the
\ if you use Windows):
PATH-TO-CONCRETE5-ROOT-FOLDER/concrete/bin/concrete5 c5:package-translate PACKAGE_HANDLE OPTIONS
PATH-TO-CONCRETE5-ROOT-FOLDERis the path of your Concrete installation directory
PACKAGE_HANDLEis the handle of your package (or the path to the directory that contains it)
--helpfor 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
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.potthis 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 since
c5:package-translateis smart enough to create the language files for you, you can ignore it.
packages/handyman/languages/it_IT/LC_MESSAGES/messages.pothis is the so-called Portable Object file that should be translated into Italian.
packages/handyman/languages/it_IT/LC_MESSAGES/messages.mothis is the so-called Machine Object file and it's the compiled version of the
packages/handyman/languages/de_DE/LC_MESSAGES/messages.posame as the file under
it_ITbut for German
packages/handyman/languages/de_DE/LC_MESSAGES/messages.mosame as the file under
it_ITbut 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
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:
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
.po files, since Concrete will only need the
.mo files. The
c5:package-pack can do this for you.