In my last Project i was in need to create a CMSMS based Website with 5 languages and as we already know MLE Fork is no longer supported or better said it is declared as dead.
To be able delivering a functional Website that didn't bring any risk of not being able to update it if needed i decided not to use MLE Fork and go with the Core version, until there isn't a better solution for this.
There are already few ML Methods that you can find in CMSMS Forums like Rolf's or Alinome's and also a Module called Babel.
Unfortunately none of the Methods was real answer to my Problem, Rolf's Method links Language pages only to "Home" page, Alinome's Method requires Content Block for page linking and Babel has not been update for over a year and not ideal for URL structure that was required by Client.
As first step we need a logical Page structure, so we are able adding pages for each Language. The strucutre is tree based and compared to MLE Fork i prefer this structure as it gives us more power over URL structure and SEO.
As you can see we have "Home" as root page, this page doesn't do much but rediercts Visitor to appropriate Page, "English", "Deutsch" and "Francaise" are simple "Internal Page Link" with alias like "en", "de" and "fr" which we will use later on for Language structure.
After we have our strucutre set we need to take care of redirecting our Website Visitors to appropriate page. This method was taken from Rolf's post or better said as RonnyK suggested.
We need a UDT named for example get_browserlang
$gCms = cmsms();
// Read browser language
$foo = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
// Only need the first two characters
$lang = substr($foo,0,2);
// Passing the parameter $lang to the template
$smarty = &$gCms->GetSmarty();
$smarty->assign('lang', $lang); Next step is to setup our root page for redirection, open the "Home" page and enter this chunk of code.
{get_browserlang}
{if $lang == 'de'}
{redirect_page page="startseite"}
{elseif $lang == 'fr'}
{redirect_page page="accueil"}
{else}
{redirect_page page="home-en"} {/if} As you can see, we call our UDT first to detect Browser Language and have parameter $lang available. The redirection works after this somethins like this, if Visitor has German language set he should get redirected to "startseite" which is our German start page with this Alias, else if Visitor is French he should be redirected to "accueil" which is start page in French and if none of both Visitor is redirected to "home-en" as Default.
Note: you can use here either Page Alias or Page ID.
Now this is the part where this method is different from those i mentioned above. As required by client i was in need to have URL structure like www.domain.com/en/this-title-is-english and www.domain.com/de/dieser-titel-ist-deutsch and being able to switch between pages using Language Menu without jumping to "Home" page of each language.
Most bullet proof method i could think of was using page Hierarchy to link between pages, without worrying that Editor would break this structure as if i would use some method with page Aliases or Page ID's.
Unfortunately cms_selflink doesn't work with Hierarchy numbers, but it works with alias, so a solution to retrieve Alias of the page by Hierarchy number was needed.
And following UDT does exactly this (thanks to Peciura for great Help)
Create a UDT named hierarchy_position for example
/**
* Returns page_alias of valid [friendly] position
*
* @param string $params['friendly_position'] Mandatory.
* @param string $params['assign']
*/
$return = FALSE;
if (!empty($params[friendly_position])){
$delim = '.';
$hierarchy_array = array();
$friendly_position_array = explode($delim, $params['friendly_position']);
foreach($friendly_position_array as $one){
$hierarchy_array[] = str_pad($one, 5, '0', STR_PAD_LEFT);
}
$hierarchy = implode($delim, $hierarchy_array);
$query = 'SELECT content_alias FROM '.cms_db_prefix().'content WHERE hierarchy = ?';
$db = cmsms()->GetDB();
$return = $db->GetOne($query, array($hierarchy));
}
if(!empty($params['assign'])){
$smarty = cmsms()->GetSmarty();
$smarty->assign($params['assign'], $return);
}
else {
return $return; } As we have all needed functions set, we can move on building our Multilingual Template.
The first thing we need is CGSimpleSmarty Module.
Let's start with <head> part.
{content assign="capturedcontent"}
{if !isset($pagetitle)}{capture assign='pagetitle'}{title}{/capture}{/if}
{capture assign='page_lang}{$cgsimple->get_root_alias()}{/capture}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{$page_lang}" >
<head>
<title>{if !empty($pagetitle)}{$pagetitle}{else}{title}{/if} - {sitename} </title>
{if isset($canonical)}
<link rel="canonical" href="{$canonical}" />{elseif isset($content_obj)}
<link rel="canonical" href="{$content_obj->GetURL()}" />{/if}
{metadata}
<meta http-equiv="content-language" content="{$page_lang}" />
{cms_stylesheet}
{cms_selflink dir='start' rellink=1}
{cms_selflink dir='prev' rellink=1}
{cms_selflink dir='next' rellink=1}
</head> So what have we done here, first we assigned content tag to capturedcontent, mostly i use content multiple times in my template and this prevents the error message in the backend about double content.
Next is the title part, used to display News module title instead fo regular page Title if we are viewing News detail page. Now the next thing is page_lang, as you can see we use here CGSimpleSmarty module to get our root alias which will result in "en", "de" or "fr".
In the doctype and metadata we use now {$page_lang} which returns values mentioned above.
Next part of Template is building our hierarchy_position UDT together so we can retrieve correct links in Language Menu.
<body>
{* language switching *}
{capture assign="hierarchy_en"}2{$friendly_position|substr:'1'}{/capture}
{capture assign="hierarchy_de"}3{$friendly_position|substr:'1'}{/capture}
{capture assign="hierarchy_fr"}4{$friendly_position|substr:'1'}{/capture}
{hierarchy_position friendly_position=$hierarchy_en assign="en_alias"}
{hierarchy_position friendly_position=$hierarchy_de assign="de_alias"}
{hierarchy_position friendly_position=$hierarchy_fr assign="fr_alias"} As you can see, first we need our hierarchy position, with substr:'1' first value is removed so from 2.1.3 would result .1.3 and we add first number of each Language, this is the starting number of our Hierarchy structure.
Next part is hierarchy_position UDT where we call our assigned hierarchy_lang inside for each language and assign it again for later use in the Language Menu.
Simply said, we retrieve the Hierarchy number of current page, remove first numer and replace it with our Lanugage Hierarchy position and retrieve Page Alias from that number.
In the next part we build our Language switch menu.
<!-- start pageWrapper -->
<div class="pageWrapper">
<!-- start pageHeader -->
<div class="pageHeader">
<div class="pageLogo">{cms_selflink dir="start" imageonly="1" alt=$sitename image="uploads/template/pageLogo.jpg"} </div>
<div class="langSelect">
<ul>
<li>{cms_selflink page=$en_alias imageonly="1" image="uploads/template/en.gif" title="English" alt="English"}</li>
<li>{cms_selflink page=$fr_alias imageonly="1" image="uploads/template/fr.gif" title="Francaise" alt="Francaise"}</li>
<li>{cms_selflink page=$de_alias imageonly="1" image="uploads/template/de.gif" title="Deutsch" alt="Deutsch"}</li>
</ul>
</div>
<!-- end pageHeader -->
</div>
So we have now the top part of our Template, a logo that links to start page, which will redirect us back to our Language and we have Language switch menu that is using above created parameters to link our current page correctly.
Next part would be building our Menu according to language like this.
<!-- start pageNavigation -->
<div class="pageNavigation">
{if $page_lang == 'de'}
{menu loadprops="0" template="pageNavigation" start_element="3.1" show_root_siblings="1"}
{elseif $page_lang == 'fr'}
{menu loadprops="0" template="pageNavigation" start_element="4.1" show_root_siblings="1"}
{else}
{menu loadprops="0" template="pageNavigation" start_element="2.1" show_root_siblings="1"}{/if}
<!-- end pageHeader -->
</div> All we have left now is our Content area and Footer and we are done.
<!-- start pageContent -->
<div class="pageContent">
{$capturedcontent}
<!-- end pageContent -->
</div>
<!-- end pageWrapper -->
</div>
<!-- start pageFooter -->
<div class="pageFooter">
{if $page_lang ne ''}
{global_content name=footer-$page_lang}
{else}
{global_content name='footer-en'}
{/if}
<!-- end pageFooter -->
</div>
</body>
</html> Thats it, we are done, complete Template look like this after we are done.
{content assign="capturedcontent"}
{if !isset($pagetitle)}{capture assign='pagetitle'}{title}{/capture}{/if}
{capture assign='page_lang}{$cgsimple->get_root_alias()}{/capture}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{$page_lang}" >
<head>
<title>{if !empty($pagetitle)}{$pagetitle}{else}{title}{/if} - {sitename}</title>
{if isset($canonical)}<link rel="canonical" href="{$canonical}" />{elseif isset($content_obj)}
<link rel="canonical" href="{$content_obj->GetURL()}" />{/if}
{metadata}
<meta http-equiv="content-language" content="{$page_lang}" />
{cms_stylesheet}
{cms_selflink dir='start' rellink=1}
{cms_selflink dir='prev' rellink=1}
{cms_selflink dir='next' rellink=1}
</head>
<body>
{* language switching *}
{capture assign="hierarchy_en"}2{$friendly_position|substr:'1'}{/capture}
{capture assign="hierarchy_de"}3{$friendly_position|substr:'1'}{/capture}
{capture assign="hierarchy_fr"}4{$friendly_position|substr:'1'}{/capture}
{hierarchy_position friendly_position=$hierarchy_en assign="en_alias"}
{hierarchy_position friendly_position=$hierarchy_de assign="de_alias"}
{hierarchy_position friendly_position=$hierarchy_fr assign="fr_alias"}
<!-- start pageWrapper -->
<div class="pageWrapper">
<!-- start pageHeader -->
<div class="pageHeader">
<div class="pageLogo">{cms_selflink dir="start" imageonly="1" alt=$sitename image="uploads/template/pageLogo.jpg"}
</div>
<div class="langSelect">
<ul>
<li>{cms_selflink page=$en_alias imageonly="1" image="uploads/template/en.gif" title="English" alt="English"}</li>
<li>{cms_selflink page=$fr_alias imageonly="1" image="uploads/template/fr.gif" title="Francaise" alt="Francaise"}</li>
<li>{cms_selflink page=$de_alias imageonly="1" image="uploads/template/de.gif" title="Deutsch" alt="Deutsch"}</li>
</ul>
</div>
<!-- end pageHeader -->
</div>
<!-- start pageNavigation -->
<div class="pageNavigation">
{if $page_lang == 'de'}
{menu loadprops="0" template="pageNavigation" start_element="3.1" show_root_siblings="1"}
{elseif $page_lang == 'fr'}
{menu loadprops="0" template="pageNavigation" start_element="4.1" show_root_siblings="1"}
{else}
{menu loadprops="0" template="pageNavigation" start_element="2.1" show_root_siblings="1"}{/if}
<!-- end pageHeader -->
</div>
<!-- start pageContent -->
<div class="pageContent">
{$capturedcontent}
<!-- end pageContent -->
</div>
<!-- end pageWrapper -->
</div>
<!-- start pageFooter -->
<div class="pageFooter">
{if $page_lang ne ''}
{global_content name=footer-$page_lang}
{else}
{global_content name='footer-en'}
{/if}
<!-- end pageFooter -->
</div>
</body>
</html> Enjoy buliding your ML Template.
with Smarty you can use PHP functions, means as a Documentation you can also use PHP Docs http://php.net/manual/en/function.substr.php
Hello,
thanks for your work! Could You give me a hint how to get to the "substr:'1'" - modifier in the {$friendly_position} variable? I cannot find it on the smarty website (http://www.smarty.net/docs/en/language.modifiers.tpl) or anywhere else.
Thanks
Dominik
Hi, Thanks for your ideas - I've not yet had time to try them out, but I wanted to let you know I am happy with the multi-hierarchy structure imposed by Babel for my first multi-lingual CMSMS site: http://www.luthier-perpignan.fr
It allows cross-linking each page between languages, and doesn't get lost if a page only exists in one or two languages.
But a side-by-side presentation for creating and modifying pages would be much better for a site that changes a lot.
I'll give your method a closer look sometime in the new year.
best regards,
Chris
@Maria and your CMSMS version is?
Hello! Please, help me, it write "Fatal error: Call to undefined function cmsms() in /pub/home/egorsoroki/htdocs/lib/content.functions.php(771) : eval()'d code on line 2" when i created a UDT in the begin, and can't do anything now =(
Hi Tony,
first of all thank you for kind words.
There shoudln't be any Problem with later CMSMS version, i upgraded the CMSMS i used this method to 1.9.4.2 without any problems.
But after my post a module called MLe CMS was released so maybe you wan't to check it out as it includes some nice solutions, i didn't have a chance to use it for a project but was helping out here and there with it and it look easy to handle. Well probably on long term a Site would be easier to maintain then with my method.
I can't say much about Babel, was following few Forum posts in Offical board which has lead me to try some other method.
It is probably like cars, either you like it or not ;-)
Goran,
Great article. I particularly appreciate your assessment of the other options from Babel or Rolf.
If you have a moment, do you know:
1. What is the latest version of CMSMS that your solution works with? Is 1.9.3 and higher a problem?
2. What are some of the disadvantages you've seen with Babel?
Thanks! Tony
Thanx a lot! This method is very elegant and works like a charm :)
(tested on v1.9.4.1)
Thanks for this! Testen and working in cmsms 1.9.4.1 :)
Greetings, Manuel
How does your page structure look like? Where do you call language UDT? Did you check for typos in {redirect_page} tag?
Thank you for this also. I have a website in 2 languages, French and English. My locale is set to french and i also have all my browser and Os in French, but when i arrive on my site, it shows the english version first. Any clue how to resolve that ?
Thank you Louis-Philippe
Hello again!
It seems, that i got a fault in my template! So the language switch only works one time.
I don't see a solution yet. But it works on 1.9.2/1.9.3 with standard template without a fault.
So thanks for your solution!
Hi Goran, i tried it again on 1.9.2 and got the same problem.
Don't see a fault on installing your solution. Do you think, you can help me out when you get a look over the code ?
Regards OG
Hi OG,
i can't recall having issues in 1.9.2 version, but it could be possible that there may be problems with 1.9.3 due to caching changes. I didn't try this method on 1.9.3
Thanks for publishing this way of multilanguage solution.
It works for me, but it occurs a problem when browser closed and reopened. After this step, the "lang switcher" doesn't work for me in CMSms 1.9.3.
Do you got an idea to solve this ?
Regards OG
Thanks for sharing this MLE idea. Worked very nice for me.
Thanks Totophe, will take a look at it ;-)
You can also use the module I18n to translate some strings from the template or global content blocks.
See http://dev.cmsmadesimple.org/projects/i18n
In this case i was in need for News and Products module, for News i only used lang="de_DE" where i called the module on each page. For the Products part i used the same method as in Page Template if language do this else this. I know it's not perfect solution but better than none, regarding babel the Problem was the URL structure and no updates for long time as i mentioned in the post.
How do you translate modules? for example gallery module. thats one main advantage of babel! it makes it possible to use just one instance of a module and translate their labels.