System requirements

This add-on requires you to have the multilingual (Internationalization) add-on installed.

You also need to have the mbstring PHP extension enabled on your system.

After installation

After installation, you should make sure that all your translation folders are BOTH readable and writable by the server process:

  • {root}/languages + all of its subfolders
  • {root}/packages/**/languages + all of their subfolders (** means for each and every package you have)

If you don't allow reading and writing to these folders some of the basic functionality will not work!

If some of your permissions settings are not correct you will get the following error message when adding or saving a translation:

An unexpected error occurred. 
Cannot write to file, check permissions

For further info, please check up the question addressing this problem in the FAQ-section.

Site, System, Package… What are these?

Different entities in concrete5 context can be translated because of which the translation interfaces have been separated into these entities. This makes managing different level of translations easier and also e.g. makes it so that your package translations travel together with your package. These entities are:

  • Site Interface - your site-specific texts e.g. in your theme and your special or overridden blocks on this site. Everything that appears on the first level of the concrete5 directory tree. Also, site-specific single page names for example.
  • System - all texts that is in the concrete5 core. This should contain everything that is in the /concrete folder (or updated core folder /updates/concreteVVVV/concrete/) of your installation.
  • Package - package-specific translations. Contains every translatable string found inside this package and also package-specific single page names, attribute names, etc.

Parsing the source

Parsing the source method does multiple thing you don’t have to worry about to keep all your translations consistent. How the process works is as follows:

  • Phase 1: All your source code is parsed through and searched for the translatable strings inside t(), tc() and t2() functions.
  • Phase 2: Dynamic database entities (e.g. attribute names, single page names and descriptions, etc.) are added to the translatable texts.
  • Phase 3: All your existing translations are compared to the found strings and updated accordingly. Deprecated (non-existing) translations are removed and new ones are added to each of your translations. Also, the code references are updated for each translation.
  • Phase 4: All your updated translations are saved and exported into .po/.mo files.

These phases are specific to your translation context, site, system or selected package and will not affect any other translation contexts.

This all happens through AJAX calls so that the whole process is split into smaller parts. This is because your server might otherwise time out during the process if parsing through big translation entities, such as the system context.

For big translation contexts, such as the system context, this process might take few minutes. It is suggested to run the parsing to concrete5 system context only once per released version number, you really don’t have to do this more often.


Translation Interface

You can reach the translation interface by clicking “Edit” next to some language in some translation context. After clicking that button you can see a list of all the strings inside this translation and you can start translating by clicking a row from the list, writing the translation for the selected string and then clicking the “Save Translation” or “Next” button.

When you’re doing translation work for the whole context, you might want to move along to the next translation quickly without touching your mouse. You can do this by clicking “CTRL + ENTER” (CMD + ENTER under Mac) when you have your cursor in the “Translation” text field.

After you’ve translated everything you want to translate, finally you should click the “Save & Export” button on bottom of the view to actually save the translations in concrete5 understandable format. This button saves all your translated string back to .po file format and also converts it to machine understandable .mo format that concrete5 reads.

Always remember to save and export your translation, otherwise the translations are not picked up by concrete5!

If you’re not familiar with translating the concrete5 interface before, you might wonder what some of the terms mean in the interface. These terms are basically specific to .po-translation format which is commonly used for PHP applications.

  • Msgid: The message identifier that is used in your PHP code to find the translatable string. 
  • Msgstr: The actual message string in this language, this is what you need to translate.
  • Fuzzy: if you’re unsure of some translation or it is incomplete, you can mark it fuzzy. This basically means that someone else still needs to review this translation before it can be added to the final translation file. Strings marked as fuzzy are not exported during the save & export process.
  • Context: The translation context of the string. Contexts only apply to strings that are wrapped within the tc() function.
  • Comments: With the comments field you can leave comments for other translators or yourself if there is something that needs to be commented on that translation string.
  • Code references: When your source is parsed, the places of the code where that string is found are also stored into the system. With the “View code references” button you can check where the string can be found in your source code. If no references are found, chances are that this translation is part of your dynamic translatable strings in this context and the actual strings are stored in database instead of PHP source code.
  • Plural forms: Opens a new dialog where you can manage the plural forms of the translatable string.
    • These only apply to texts that are wrapped within the t2() function. Generally you should only care about the "Default Plural Format" but some languages might require you to define other formats as well.
    • Please note that the numbers you can define for each string form in this view ARE NOT the actual numbers that the strings are evaluated against. These numbers refer to the po-format number and the "Plural Forms:" definition of the translation file defines how they are used. By default "0" evaluates to the single format of the text and "1" evaluates to the plural format.
    • If single and plural formats (two formats in total) are the only formats you need to define for the string, you don't have to care about the additional formats. Just use the default "Translation" field to define the single format translation and "Default Plural Format" field in the popup to define the plural format.


In the translation interface you have “Actions”-button on top of the view where you can find some actions you can run for that translation. Currently there is only one translation-specific group action but in the future there might be more.

  • Update from file - You can use this button if you have e.g. parsed the source, added the new translation but you already have a previous .po file which you want to use as a basis for this translation. By updating the translation with this button, all msgid and msgstr string are searched from the provided .po file and all the msgsts in the current translation are updated to those found from the provided file.

What are considered as translatable dynamic database entities?

As mentioned before, this add-on also includes some dynamic texts in your translatable texts that are stored in your database. Please note that these entities are handeled specifically to your translatable context, so e.g. package-specific single page names are not included in your site-specific translations.

Currently the following entities are included:

  • Common static texts in system context (e.g. month and day names)
  • Single page names and descriptions
  • Page Type names
  • Attribute Type names
  • Attribute Category names
  • Attribute Set names
  • Attribute Key names and handle names
  • Block Type names and descriptions
  • Theme names and descriptions
  • Area names
  • File Set names
  • User Group names
  • Stack names
  • Automatic Job names and descriptions

If we’ve missed something here, feel free to drop us a message on the forums section of this add-on.

Extra dynamic database strings from packages


If your package has some special dynamic strings to translate that your users will be adding, you can dynamically find these strings in your package's controller and include them either in the site-context translations (suggested for user-defined strings) or your package translations. Please, do not duplicate any of the strings listed above in the return array of this method.

To do this, just define this method in your package's controller (/packages/your_package/controller.php):

public function getDynamicTranslatableStrings() {
  $strings = array();
  // TODO: Add your dynamic strings here, it's no use to add any static strings here
  // For example:
  $mll = new MyModelList();
  foreach ($mll->get() as $myModel) {
    $strings[] = $myModel->getModelName();
  return $strings;

If you want to include any dynamic strings in your package translation files, just add the same kind of method in the controller with name "getDynamicTranslatableStringsPackage()".


It is now also possible to define the translation contexts and/or plural formats in the dynamic strings as follows:

public function getDynamicTranslatableStrings() {
  $strings = array();
  // Plural format
  $strings[] = array('msgid' => "Singular format", 'msgid_plural' => "Plural format");
  // Translation context
  $strings[] = array('msgid' => "Singular format", 'msgctxt' => "TranslationContext");
  return $strings;

Concrete5 does not currently support translating some of the entities

In the current version of concrete5, all the dynamic entities do not translate correctly because they are not wrapped into the t() function in the core. There’s not much we can do about this than to provide a patch for this issue.

This is a known bug by many multilingual developers and it is discussed in the bug reporting section of concrete5.5.2.1. We’ve also made a patch to solve this issue for that concrete5 version, and you can find it in that bug thread:

Notes for concrete5 developers on translatable strings

When developing for concrete5, you should wrap all the texts output from your code for the user inside the t() function that concrete5 uses to translate strings. This also applies to dynamic texts that are common for each language but stored in your database, for example attribute key names.

For strings that contain some dynamic text or dynamic number that changes depending on the attributes, you should use the PHP-format to display the dynamic sections of the translatable text. For example:

  • t('You have uploaded %d files into your file manager.', 32) => "You have uploaded 32 files into your file manager"
  • t('Please press the %s button to proceed', t('Next')) => "Please press the Next button to proceed"
  • t('%d of your %d uploads are complete, complete percentage: %.2f', 4, 35, 100*4/35) => "4 of your 35 uploads are complete, complete percentage 11.42"

To keep the translatable texts in standard and correct format, you should not escape anything in the string you wrap inside the t() function in general. If you need to escape e.g. ' or ", don't do it. Instead, wrap the quotes into the other quotes you don't need to include, for example:

  • $str = t('My string with "quoted" word')
  • $str = t("My string with 'quoted' word")

This keeps the translation .po files in standard and correct format so that you will not get any parsing exceptions if opening the .po files in external translation tools. There are exceptions to this tip but if you need to worry about them, you probably know what you're doing.

Some of my strings don’t translate!

Sometimes doing translation work you might wonder why some of the newly translated strings are not translating actually although you have saved and exported the translation file. Here are some tips to debug this situation:

  • Check that the string is not marked fuzzy in the selected language
    • Fuzzy translations will not be exported to .mo files because they need more care
  • Check that the string in question is actually in translatable context
    • Wrapped in t() function in a PHP file that is contained in the context
    • Database entity in that context (check the dynamic database entities section)
  • Check all other translations in every context (and every add-on in the package context) that the same string is not included in them in un-translated format for the same language
    • If found, translate the string in every context and save and export the file
    • If not found, really, look again, this is the most usual reason
  • If the string is dynamic translatable string (e.g. page name, attribute name), make sure you're running a concrete5 version that is able to tranlate these dynamic strings, you can check this from the straight source code and see whether they are wrapped inside the t() function
  • Make sure you have saved and exported each and every one of the translation files where the string appears
  • Clear your system's cache and refresh the page
    • The tranlation saving process usually clears out the cache already but just in case it has not been cleared
  • If the string appears in the dashboard dropdown, try logging out and logging in again
    • The dahsboard dropdown is saved into a session variable in concrete5
    • This should be cleared and refreshed when saving the translation but just to make sure, you should try this
  • Restart your web server process
    • Some web server software saves the translations into its own cache, this cannot be cleared in any other way than restarting your web server process, waiting for the cache to clear or contacting your service provider if you don't have access to restart the server process
    • Sorry, there’s nothing we can do to avoid this, this is just how some web servers are setup to work


My progress bar got stuck during the source parsing!
This might happen if you’re running behind quite slow server. There are settings variables you can change in order to change this but the parsing process is quite heavy so we suggest running it on a server capable of processing requests that might take 5 seconds of time at least, depending on your other concrete5 configuration.

I have existing translations in my languages folder that do not appear in the Translations Manager
If you have added manually translations to your system, site or package context before installing the Translations Manager and they don't appear in the translations lists, you haven't probably allowed your server process to read the languages directory or some of its subdirectories. When using translations manager you should always have provided read and write access for the web server process to access your translation files and directories.

Why do some of my translations show 0% or “Unknown” in the complete column?
This is basically because you might not have translated or opened the translation yet. If the column says “Unknown”, this is because you might have translations added in your different contexts before installing the Translations Manager. You can fix the unknown columns by editing that translation for the first time and for the 0% columns, you need to start translating that language. 

I’ve translated everything but the translation is still not 100% complete?
With some languages and translation contexts you might never reach 100% complete percentage. This is count based on how many strings you have that have different msgid and msgstr fields from each other. If these are same for some of your translations, you will never reach 100%. Also, in the dynamic translatable content there might be some strings that you don’t even need to translate because of which you are not in 100%.

Why do I get "Permissions denied" error when adding or saving a translation?
"An unexpected error occurred. Cannot write to file, check permissions"
The reason for this is quite obvious and it's because you have incorrect file permissions for the server to be able write to your translation files. If you've already changed the permissions to these directories to be the correct ones, your server is probably set so that the server process running your site is neither the owner of these directories/files nor in the owner group of these directories/files. The following fix is not suggested (explained below why) but it will TEMPORARILY fix this problem. Add this to your config/site.php:

define('FILE_PERMISSIONS_MODE', 0666);

Why this is not suggested in production environment is the "devils number 666". This means everyone who has access to your server, will be able to read and write to EACH AND EVERY FILE concrete5 now creates. This is a real security thread especially in shared hosting environments but trying this at least lets you see where the problem is. A more suitable solution to this for a live production environment would be to add the server process user to be in the owner group of these directories and files when they would not be accessible for each and every user that has access to your server.

Parsing the source or saving translations produces HTTP 500 error
It is likely that you do not have the PHP's mbstring extension enabled that this add-on requires. Please check the phpinfo of your server and search for "mbstring" or "Multibyte String". If you cannot find this extension enabled, you will need to enable it.

The installation routine of this add-on should already make sure that this requirement is fulfilled but in case you have moved your site to another server recently, it is possible that this might still happen.

Some .po-files are not in the standard format and produce errors when saving with Poedit.
When opening and saving a .po file generated with Translations Manager into Poedit software, you might get some syntax errors when saving the file. Usually these syntax errors don’t matter, the file is still saved if you want to use Poedit for some reason. This can happen because the original string is not written in correct format by the developer who has wrapped it into the t()-function. For more information check the “notes for concrete5 developers” section of the documentation. If you change these strings into correct format that does not produce errors in Poedit, they will not be translated by concrete5 because they still appear in incorrect format in the PHP source code and the strings are looked up with the incorrect format.