We continue posting Magento certification-dedicated articles in our blog. This time we will describe how to internationalize your Magento site.
Magento can manage multiple stores in a single installation. There are three layers in this hierarchy – websites, stores and store views.
The top level is Website. It’s made up of one or multiple stores. They in turn are made up of one or multiple store views. Stores have the same customers, orders and shopping carts. They are set of store views and the main idea of stores is to group store views in a website.
Most of Magento installations have only one website with one store and only one store view. As store Views are the actual store instances, they are typically used for internationalization purposes, i.e. translation of your store into different languages. Therefore, if you need to display your store in multiple languages, for example English, French and German, you should create the store once and then create three different store views for this particular store.
New store view can be created via Magento Admin panel. Log into it, select System -> Manage Stores and click Create store view.
Next, you need to fill the form and select Store, enter your store name, its code, status, sort order and then Save Store View.
After that you need to go to System -> Configuration, choose the needed store view and find Locale options section under General tab. Next, from Locale drop-down, select the right one (the first comes language, the second – country, for example, for Canadian English it would be English (Canada)).
Next, you need to download translation package/files. They can be downloaded from the official Magento site, either from Translations directory added by contributors or from Magento connect added by Magento core team.
After refreshing caches we’re all set; in default Magento, the store view can be accessed via http://yoursite.com/index.php?___store=store_code. There is also a setting to add store_code into URL like this http://site.com/index.php/store_code. It can be set up in System -> Config -> Web -> Url Options -> Add Store Code to Urls.
The use of Magento translate classes and translate files.
Everywhere in Magento templates (.phtml files) you can see something similar to this one $this->__(‘some string’). Actually it’s Mage::helper(‘core’)->__()
Let’s see how it looks like.
app/code/core/Mage/Core/Helper/Abstract.php
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * Translate * * @return string */ public function __() { $args = func_get_args(); $expr = new Mage_Core_Model_Translate_Expr(array_shift($args), $this->_getModuleName()); array_unshift($args, $expr); return Mage::app()->getTranslator()->translate($args); } |
So it calls getTranslator and retrieves translate object.
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * Retrieve translate object * * @return Mage_Core_Model_Translate */ public function getTranslator() { if (!$this->_translator) { $this->_translator = Mage::getSingleton('core/translate'); } return $this->_translator; } |
And then translate() method that returns the resulting text in app/code/core/Mage/Core/Model/Translate.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
/** * Translate * * @param array $args * @return string */ public function translate($args) { $text = array_shift($args); if (is_string($text) && ''==$text || is_null($text) || is_bool($text) && false===$text || is_object($text) && ''==$text->getText()) { return ''; } if ($text instanceof Mage_Core_Model_Translate_Expr) { $code = $text->getCode(self::SCOPE_SEPARATOR); $module = $text->getModule(); $text = $text->getText(); $translated = $this->_getTranslatedString($text, $code); } else { if (!empty($_REQUEST['theme'])) { $module = 'frontend/default/'.$_REQUEST['theme']; } else { $module = 'frontend/default/default'; } $code = $module.self::SCOPE_SEPARATOR.$text; $translated = $this->_getTranslatedString($text, $code); } //array_unshift($args, $translated); //$result = @call_user_func_array('sprintf', $args); $result = @vsprintf($translated, $args); if ($result === false) { $result = $translated; } if ($this->_translateInline && $this->getTranslateInline()) { if (strpos($result, '{{{')===false || strpos($result, '}}}')===false || strpos($result, '}}{{')===false) { $result = '{{{'.$result.'}}{{'.$translated.'}}{{'.$text.'}}{{'.$module.'}}}'; } } return $result; } |
Developer mode influence on Magento translations.
Let’s see how Magento initializes translation.
app/code/core/Mage/Core/Model/Translate.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * Initialization translation data * * @param string $area * @return Mage_Core_Model_Translate */ public function init($area, $forceReload = false) { $this->setConfig(array(self::CONFIG_KEY_AREA=>$area)); $this->_translateInline = Mage::getSingleton('core/translate_inline') ->isAllowed($area=='adminhtml' ? 'admin' : null); if (!$forceReload) { if ($this->_canUseCache()) { $this->_data = $this->_loadCache(); if ($this->_data !== false) { return $this; } } Mage::app()->removeCache($this->getCacheId()); } $this->_data = array(); //Loading data from module translation files foreach ($this->getModulesConfig() as $moduleName=>$info) { $info = $info->asArray(); $this->_loadModuleTranslation($moduleName, $info['files'], $forceReload); } $this->_loadThemeTranslation($forceReload); |
1 2 3 4 5 6 7 8 |
$this->_loadDbTranslation($forceReload); if (!$forceReload && $this->_canUseCache()) { $this->_saveCache(); } return $this; } |
As we can see, Magento supports and loads three different types of translations data – Module translation, Theme translation and DbTranslation. Let’s look at one of these methods more thoroughly: _loadModuleTranslation (just need to be mentioned that all three methods call _addData method inside their code).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Loading data from module translation files * * @param string $moduleName * @param string $files * @return Mage_Core_Model_Translate */ protected function _loadModuleTranslation($moduleName, $files, $forceReload=false) { foreach ($files as $file) { $file = $this->_getModuleFilePath($moduleName, $file); $this->_addData($this->_getFileData($file), $moduleName, $forceReload); } return $this; } |
And finally, in this method we can see how Magento adds translation data. Inside of this method, Magento checks whether developer mode was set to true (if (Mage::getIsDeveloperMode())) and, if so, it’s not allowing to use translation, not related to module (i.e. unset data array).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/** * Adding translation data * * @param array $data * @param string $scope * @return Mage_Core_Model_Translate */ protected function _addData($data, $scope, $forceReload=false) { foreach ($data as $key => $value) { if ($key === $value) { continue; } $key = $this->_prepareDataString($key); $value = $this->_prepareDataString($value); if ($scope && isset($this->_dataScope[$key]) && !$forceReload ) { /** * Checking previous value */ $scopeKey = $this->_dataScope[$key] . self::SCOPE_SEPARATOR . $key; if (!isset($this->_data[$scopeKey])) { if (isset($this->_data[$key])) { $this->_data[$scopeKey] = $this->_data[$key]; /** * Not allow use translation not related to module */ if (Mage::getIsDeveloperMode()) { unset($this->_data[$key]); } } } $scopeKey = $scope . self::SCOPE_SEPARATOR . $key; $this->_data[$scopeKey] = $value; } else { $this->_data[$key] = $value; $this->_dataScope[$key]= $scope; } } return $this; } |
How many options exist to add a custom translation for any given string?
As mentioned above, there are three options in Magento to add a custom translation to a text string: module translation, theme translation and inline translation.
1. Module translation
Module translations are stored in app/locale/languagecode_COUNTRYCODE/ folder in form of csv files, named as Namespace_Modulename.csv All string in extensions that are inside __() method can be translated this way
2. Theme translation
Strings can be translated inside your theme, for that you just need to set locale via Magento admin area, then create translate.csv in app/design/frontend/<package>/<theme>/locale/ languagecode_COUNTRYCODE and put your translated strings inside this CSV
“My Cart”,”My Basket”
“My Account”,”Account”
There are a lot of tips&tricks that can be described on this subject (module and theme translations), so I feel like they deserve an individual article. Stay tuned!
3. Inline translation
To enable inline translation you need to log into Admin panel and go to System -> Configuration -> Developer.
You need to select Store view from scope select and enable inline translation for this desired store view there.
All translation made by this method will be stored in core_translate table inside your database. In order to understand better how this method works, check this video out.
Translation options priority.
As we saw in intit() method from
1 2 3 4 5 6 7 8 |
//Loading data from module translation files foreach ($this->getModulesConfig() as $moduleName=>$info) { $info = $info->asArray(); $this->_loadModuleTranslation($moduleName, $info['files'], $forceReload); } $this->_loadThemeTranslation($forceReload); $this->_loadDbTranslation($forceReload); |
It means that Magento loads translations in the following order:
- CSV in /app/locale
- CSV in /app/design/<area>/<package>/<theme>/locale (theme folder translate)
- Database (table core_translate)
Which, in turn, means that inline translation (table core_translate) has the highest priority, then goes translate.csv from your theme folder and finally module translations from app/locale.
What are the advantages and disadvantages of using subdomains and subdirectories in internationalization?
Normal
0
false
false
false
RU
X-NONE
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:”Обычная таблица”;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:””;
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:none;
text-autospace:ideograph-other;
font-size:12.0pt;
font-family:”Liberation Serif”,”serif”;
mso-font-kerning:1.5pt;}
Subfolders (yourstore.com/uk, yourstore.com/ru)
Advantages
- Separate folders can be still targeted.
- Easier linking process.
- All SEO efforts and advertisements will affect all subfolders the website will even count towards the power of the subdirectories.
- Easy to manage.
Disadvantages
- SERP limitations.
- On large sites, the URL anatomy can be quite messy (if handled well, it can look very slick though).
- Unable to benefit from hosting different parts of the site in different countries.
Subdomains (uk.yourstore.com, ru.yourstore.com)
Advantages
- Cost (only one domain is used).
- Individual subdomain can be hosted in different countries (there is a belief amongst SEO people that it’s a great idea to host sites inside).
- Better usability (recognizable country from the domain).
Disadvantages
- Extra link building is required.
- Maintaining several subdomains is more difficult.
Before August 2011, the main difference between these two options was that Google considered subdomains as external links, however now it seems that they’re internal links:
http://googlewebmastercentral.blogspot.ca/2011/08/reorganizing-internal-vs-external.html
Hey Sergei,
If you need an online tool to translate .csv files in a team, I warmly recommend you check out the localization management platform
I think it’s one of the simplest solutions to automate localization projects.
Thanks for a great information in your blog. I have read all the post of your blog. Great work on Magento.
Thank you so much for the article,it means a lot to me.
I think belvg is one of the best references for fronted development certificate
From Venezuela. Did you publish the next article about Theme translation?
Thx. Sergei, the post is great!!!
Hi Sergei ,
Nice and detailed post ,thanks for sharing .
Hi Sergei, thank you for this article!
I have a question about internationalization: Is it possible to use ‘Module translation’ in the email templates? For me it would be useful, because the html part is always the same for all the languages, and I would like to have all the email text elements together in a csv file…
Any suggestions are welcomed!
Thank you in advance!
Yes Patt, you’re absolutely right.
When you mention about the inline has higest priority, is it because the DB Inline translation loaded as a last in the order, hence it would override the prior tranlastion?
Hi Eliacim, thanks for kind words!
the first option Inline Translation? – absolutely, inline translations have top priority, then goes theme translation and then from locale folder
Hi! Thank you very much for your posts, its had really helped me a lot in my preparation for the exam. Their contributions are very very welcome. On this issue of the translation, I got lost at one point. Say that Magento loads the translations in order of: first the Module Translation , then Theme Translation, and finally the Inline Translation. However, the priority in deciding what show, is reversed and the first option Inline Translation?
Alex, multi-store setup is not connected to the internationalization question. Sergei described SEO and UX parts of subdomains vs subdirectories setups.
If you are looking for instructions how to setup multi-store you can referrer to the magento knowledge base:
Tutorial: Multi-Site, Multi-Domain Setup
Video: Creating Multiple Online Storefronts: part 1, part 2
But you did not explain how to use subdomains and subdirectories.