Multilingual template for CMS Made Simple
Previous article Next articleIn this article I will try to give a description of my second approach to create a multilingual website with basic CMSMS functions.
You can find my previous method here at the CMSMS Tips & Tricks board
I tried to make this one as simple as possible, if you need it you can add other features yourself!
Explaining how to create a MLE-template really can't in 10 lines, so I have tried to make it as clear as possible. Don't be frightened by the size of the article and the amount of code. When you take a closer look at it, you will see the minor changes you have to make to let it all work.
How to use
1. Install pretty URL
First you have to enable pretty url at your website using mod_rewrite.
Read all about it in this tutorial
2. Create a new blank Core::Page template
We need this for the landing page of the website to prevent warning messages in the admin log.
(Tip: you can also use it for dynamic sitemaps etc.)
In your Admin panel you go to Layout >> Design Manager, open the tab Templates and create a new Core::Page template with the only content:
3. Page structure
Makes your content pages in this structure:
- 1. Landingpage [set as default page]
- 2. Start [Important: set page alias in options tab to 'nl']
- 2.1 Start [Set Internal Link to 2 ^ ]
- 2.2 Page 1
- 2.3 Page 2
- 3. Accueil [Important: set page alias in options tab to 'fr']
- 3.1 Accueil [Set Internal Link to 3 ^ ]
- 3.2 Page 1
- 3.3 Page 2
- 4. Anfang [Important: set page alias in options tab to 'de']
- 4.1 Anfang [Set Internal Link to 4 ^ ]
- 4.2 Page 1
- 4.3 Page 2
- 5. Homepage [Important: set page alias in options tab to 'en']
- 5.1 Homepage [Set Internal Link to 5 ^ ]
- 5.2 Page 1
- 5.3 Page 2
4. The landingpage
To redirect visitors to a homepage in their own language, you have to add two tags in the content block of your (1.) landingpage. It is better to turn off WYSIWYG...
Change in the options tab the template to "Blank template" that you just created.
{redirect_page page=$browser_lang}
5. GCB with country flags
Create a Global Content Blocks with code for showing the country flags and links.
{cms_selflink page='nl' image='uploads/template/nl.jpg' imageonly=1 alt='Nederlandstalige versie'}
{cms_selflink page='fr' image='uploads/template/fr.jpg' imageonly=1 alt='Version Française'}
{cms_selflink page='de' image='uploads/template/de.jpg' imageonly=1 alt='Deutschsprachige Version'}
{cms_selflink page='en' image='uploads/template/en.jpg' imageonly=1 alt='English version'}
</div>
6. GCB's for the footer
Create a Global Content Block for the footer code, one for each language
- footer_nl
- footer_fr
- footer_de
- footer_en
7. GCB's for the sidebar
Create a Global Content Block for the sidebar code, one for each language
- sidebar_nl
- sidebar_fr
- sidebar_de
- sidebar_en
8. Create a User Defined Tag: "get_root_page_alias"
This is important!
CMSMS has to know which language is connected to each page.
In this method I use the page_alias of the root pages for that!
I hereby use a UDT out of another blog post of mine: get_root_page_alias »
Create this User Defined Tag at your website!
9. Core::Page template
One small page template is enough! You're kidding? Noop. Amazing!!! :D
Note the difference between the quotes `, ' and " in the Smarty tags.
{process_pagedata}
{$lang = "{get_root_page_alias}" scope=global}
{/strip}<!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="{$lang}">
<head>
<title>{title} - {sitename}</title>
{metadata}
{cms_stylesheet}
</head>
<body>
<div id="header"><h1>{cms_selflink page=$lang text='My website name'}</h1></div>
<div id="main">
<div id="sidebar-right">{global_content name='flags'}</div>
<div id="sidebar-left">{global_content name="sidebar_`$lang`"}</div>
<div id="content">
<h2>{title}</h2>
{content}<br />
</div>
<div style="clear: both"></div>
</div>
<div id="footer">{global_content name="footer_`$lang`"}</div>
</body>
</html>
Well, that's all you need to do for a multilingual website with basic CMS Made Simple functions.
Now I will show you some optional extensions for the template.
Optional extensions
1. Error 404 page
When you have an error 404 page in the root of the website pages, it isn't a part of a language tree. Therefore the template misses a value for the $lang parameter and doesn't show all template parts.
To solve this, simply add in the top of the template, just behind the line {get_root_page_alias assign='lang'} the following line:
Where "error404" is the page alias of the error page.
With this line the browser language will be used for the page.
2. Global Language Strings
You can have language strings that automatically change with the language setting.
I will describe two possible methods! The first one is the easiest and can be used when you only have a few language strings. The second one needs an extra UDT and is very useful when you have many language strings. It is up to you what to use!
For both(!) methods you can use the multilingual language strings in your pages and templates like
{$languagestring.sitename} instead of {sitename}
The output will change depending on the language of the website page.
Method #1
Add to the top of your HTML-template, but under the line {$lang = "{get_root_page_alias}" scope=global}:
Create Global Content Blocks with WYSIWYG off:
{$langstring.sitename = 'Your site name in Dutch' scope=global}
{$langstring.youarehere = 'U bent hier' scope=global}
{$langstring.foo = 'bar' scope=global}
{$langstring.sitename = 'Your site name in French' scope=global}
{$langstring.youarehere = '...' scope=global}
{$langstring.foo = 'bar' scope=global}
{$langstring.sitename = 'Your site name in German' scope=global}
{$langstring.youarehere = '...' scope=global}
{$langstring.foo = 'bar' scope=global}
{$langstring.sitename = 'Your site name in English' scope=global}
{$langstring.youarehere = 'You are here' scope=global}
{$langstring.foo = 'bar' scope=global}
Method #2
For the second method we need a small User Defined Tag, create one in Extensions >> User Defined Tags named set_languagestrings.
$langstrings = $smarty->fetch('globalcontent:languagestrings_' . $lang );
$langstring = array();
$strings = preg_split('/((\r?\n)|(\r\n?))/' , $langstrings);
foreach ($strings as $string)
{
$part = explode('|' , $string , 2);
$langstring[$part[0]] = $part[1];
}
$smarty->assignGlobal('langstring',$langstring);
Add to the top of your HTML-template, but under the line {get_root_page_alias assign='lang'}:
For each language you need create a Global Content Block - with WYSIWYG off - like:
youarehere|You are here
foo|bar
youarehere|U bent hier
foo|bar
You can have as many lines as you need!!
3. Breadcrumbs
If you need breadcrumbs at your website add the following tag in your template. You need to import the breadcrumbs.tpl menu template in the Menu Manager module Admin page in the database in order to change it and add the correct MLE language string for the "You are here" text.
Comment Form
ReviewManager
ReviewManager
33 Comments
Hi Rolf,
thank you very much for this still relevant guide. I followed it (well, almost). Especially my solution for language strings is different: Since I would like to have editors to edit them in the backend I created normal content pages (one for each language) in my so called 'globals' tree aside from the language hierarchy. The contents are styled like conventional ini-files (Deutsch = German). I read the strings using the $contentobj and parsed them by parse_ini_file() directly into an array (langstrings['Deutsch']= 'German'). Very straight forward.
Moreover, I am using another udt to output the translated strings. This udt will output the original string (the key of the $langstrings array) if no translation is found to avoid empty output.
So far so good. However, I realized that the translations are not present in the detailed pages of News and Events (All templates/pages of modules with the 'mact' parameter in its URL). Since the translations are present in the output of the summary templates, I assume that these templates are initialized differently). It is obvious that this behavior is related to the different scopes (your explanations are very useful on this) and that modules are read even before the the main template (Core::Page template), where my translations are initialized.
My workaround is to initialize the translations in the detail templates of the modules as well. Its a little bit more complicated to get the right language indicator, because the module templates are not in language tree (mycmsmssite.com/calendar/... instead of mycmsmssite.com/en/calendar/...).
I just want to ask if there is a way to determine the language and to initialize the languagestrings even before the modules are loaded? Or is is necessary to write a certain module? But this has to be initialized before as well. To define the content var globally works only the other way round (from module to core template).
I also played with config['content_processing_mode'] but without success.
Greetings to all cmsms users/designers/developers :-)
Of course, Tristan! This blog "runs on coffee"!
Great article, can we bribe you with extra cups of coffee to update this for the non GCB installs?
Rolf,
Got it working now. Did forget to assign the GCB's to right template. Even responsive!
Still getting to the language homepage when the flags are clicked, not to the relative page in the different language. Will figure that out;-)
Cheers!
Hi Rolf,
Thanks for the article.
I finished editing the page template. But the menu won't show. The pages will show in a drop down menu when I check 'show in menu' on the pages with the language alias (NL, EN and DE) only.
What do I miss?
Thanks, Bert
Hi Rolf,
Great article!
Now I am trying to connect the page with the same in another language. I have read your forum post, as I can see there are content fields so that editor should fill in other languages page aliases. But is there simpler solution, like can we use current page hierarchy position and then jump to the same position in another language? Can you share your thoughts or code for achieving this? Thanks!
{$langstring.sitename = 'Your site name in Dutch' scope=global} must be:
{$languagestring.sitename = 'Your site name in Dutch' scope=global} to work.
@Marco
I updated the post, removed the assign parameter.
Thanks!
I changed the following in the 9. Core::Page template because
{$lang = "{get_root_page_alias}" scope=global} didn't work for me.
{get_root_page_alias assign=lang scope=global} did solve the problem ;).
And the flags template should contain titles as well to show the right text on mouseover:
{cms_selflink page='nl' image='uploads/template/nl.png' imageonly=1 alt='Nederlandstalige versie' title='Nederlandstalige versie'}
{cms_selflink page='fr' image='uploads/template/fr.png' imageonly=1 alt='Version Française' title='Version Française'}
{cms_selflink page='en' image='uploads/template/en.png' imageonly=1 alt='English version' title='English version'}
Just knitpicking ;)
can you buid modue Multilingual
Good question. I haven't tested this tutorial against 2.x yet. I dont have a MLE website myself anymore.
But some parts, like the UDT, are already revised. Others still need some work.
When I browse the tutorial, I notice a few minor things that have to be changed for CMSMS 2.x
1. {Menu ...} should be {Navigator ...}
2. Some Smarty scope changes need to be done to assigning global variables.
Not sure what I do with this and some other tutorials. Supporting both CMS Made Simple 1.x and 2.x in this kind of tutorials isnt that simple... Perhaps only 2.x because new websites are build with that... :-/
I will give it some thought.
But the major part of this MLE solution should work in 2.x!
Is this also working for CMSMS 2.0 ?
Hello Rolf,
I use this thechniques for almost all my websites.
But i always seem to run in to probs if i want a module like ListIt2 to follow the link structure.
For example the prefered url structure of my website is:
NL
- NL home
- NL pages
- NL Listit2 instances
FR
- FR home
- FR pages
- FR ListIt2 instances
if you set your listit2 url-prefix to "nl/listit2" the module doesn't work well: detailpages use the summary-template.
I think some how this could be solved with some htacces url rewriting in combination with smarty modifiers but i don't seem to find any info on this.
@Paul That I described in my post at the CMSMS forum: http://forum.cmsmadesimple.org/viewtopic.php?f=4&t=48112
To all: I updated the blog post with a few new features, including Hendrik's language string method.
Thanks, Rolf
hello
how to make country flags moving to same article but in another language? currently flag redirects to language home site
hi,
i wanted to use your version of multilingual, but i have an older version of cmsms (1.6.10), and i have the error message
"Fatal error: Call to undefined function cmsms() in xxxx/content.functions.php(771) : eval()'d code on line 1"
i have checked all parameters but i don't find my error.
perhaps you have an idee to rescue me :)
doc'
@Bajt
Check the page structure as described above in #2
Yours must be wrong...
Hi, great solution. Nice job!
I just have one problem. After I set everything as it should be, my urls look like www.site.com/landing/en.html. But I would like to get something like www.site.com/en.html. Is it posible to do this with CMSMS? I tried to look to mod_rewrite for a solution but with no luck so far. Any ideas?
Thanks!
Awesome, I've had the exact same solution to multi lingual sites, but the "get_root_page_alias" UDT you wrote is much more sophisticated than mine.
Anywho, I'd like to make the following contribution:
Instead of actually assigning vars in the language specific global content blocks, how about we just put the plain words there and use a another UDT to assign the vars, like so:
{* For German, we create a global content block named "langstrings_DE", containing the following: *}
Ich besitze
ein Haus
eine Wohnung
{* For English, we create a global content block named "langstrings_EN", containing the following: *}
I own
a house
an apartment
{* Now in the top of our template we put: *}
{get_root_page_alias assign=lang}
{setlangstring l="`$lang|upper`"}
Now whatever translation we need, we can access like this:
{$l.1} {$l.3}
Which would give us
"Ich besitze eine Wohnung" for German ($lang=de)
and
"I own an apartment" for English ($lang=en).
All we need now is our UDT "setlangstring" which assigns our array var $l (that's a lower case L):
$g = $smarty->fetch('globalcontent:langstrings_'.$params["l"]);
$i = 0;
$l = array();
foreach(preg_split("/((\r?\n)|(\r\n?))/", $g) as $line){
$i++;
$l[$i] = $line;
}
$smarty->assign('l',$l);
We're not using $l[0] so we can simply use the line numbers when accessing the array.
The obvious downside of this system is that we don't actually give the vars meaningful names like {$l.houseTrans}
This can easily be added:
In the global content block add name of the var, then a colon (:) and then the translation. Like so:
houseTrans:ein Haus
In the UDT code we one line to split each $line further, remove our counter var $i and have:
$g = $smarty->fetch('globalcontent:langstrings_'.$params["l"]);
$l = array();
foreach(preg_split("/((\r?\n)|(\r\n?))/", $g) as $line){
$t=explode(':', $line, 2);
$l[$t[0]] = $t[1];
}
$smarty->assign('l',$l);
Finally, we can access our translation of house with
{$l.houseTrans}
It's advisable to turn the WYSIWYG off for your global content blocks, as we don't want any HTML tags to mess with our vars. However, it's totally possible to use HTML code as a string for each var, like:
houseTrans:ein Haus
BTW, the 'explode' only takes the first colon into account, so assigning a var like myTest:5:4 will result in {$l.myTest} containing the value '5:4'.
Hope this makes life a little easier :)
Nice addition, Hendrik
Thanks!
UPDATE: To prevent error messages in the Admin Log like "Global Content Block: footerNO RESULT - Can not open or does not exist!" make a small change to the UDT described above at item 7!
Just wanted to say thanks! I just finished my second multi-language website.
With the help of this blog I can now easily build a multi-language site. It was really the last thing I needed for CMSMS.
If you are interested in the websites let me know.
rolf, is het zo dat uw voorbeeld je niet brengt naar de juiste pagina in de andere taal enkel naar de home van deze taal
of mis ik iets ?
One more question. I want to call a languagestring (a single word) from the languagestring global content block, from a UDT.
How is this possible?
I need to assign some variables on a page. They need to be language dependant:
The following is not working:
{assign var='pagedescription' value=$item->omschrijving_$lang}
or:
{assign var='pagedescription' value=$item->omschrijving_`$lang|upper`}
It gives errors. How to properly fill in the $lang value here?
I now use this:
{if $lang == 'nl'}
{assign var='pagedescription' value=$item->omschrijving_nl}
{elseif $lang == 'en'}
{assign var='pagedescription' value=$item->omschrijving_en}
{/if}
I want to make the whole CMS as dynamic and easy as possible, so the if/ then statement is not the nicest option.
If you enter the page it now redirects to: http://www.websitename/nl.html
Is it possible to redirect to: http://www.websitename/
So without the "nl.html".
Would be nicer and better for SEO I think?
I never use an extension in the url myself. If it works without, what is the point adding them... ;-)
But a workaround could be to change the "root" languagepages to an internal link (i.e. 2. for Dutch) and change the internal links (i.e. 2.1 for Dutch) to a content page. Now all URLs will be like website.com/nl/foo.html
I already got it figured out.
I removed this line from the landingpage:
{redirect_page page=$browser_lang}
Then where I need a language on the landingpage I fill in my default language:
{global_content name="sidebar-nl"}
Thanks alot, the Languagestrings works great! Much better solution than using different templates or if/ else statements.
In the 1.11.5 release of CMSMS you can only use letters and numbers in the name of a Global Content Block. So I removed the dash from the GCB names. For readability I changed the languagepart in the name to capitals.
Function confirmed, I added it to the article!!
Just a thought, what about a global languagefile in a GCB...
Call it in the top of your template like {global_content name="languagestrings-`$lang`"}
WYSIWYG should be switched off!
GCB #1, named languagestrings-nl:
{assign var='material' value='Materiaal'}
{assign var='foo' value='bar'}
GCB #2, named languagestrings-en:
{assign var='material' value='Material'}
{assign var='foo' value='baz'}
etc.
You can use in the template {$material}. Depending on the value of $lang a different languagestring value will be used...
Hi,
I'm currently trying to implement this method.
I have two question's.
In a module which was generated with the module generator I show the contents of a field like this:
{$item->omschrijving}
How can I change this with the $lang variable? Because I now have made 2 separate fileds:
omschrijving_nl and omschrijving_en
I tried several things like: {$item->omschrijving_$lang} but nothing works. I can off course use a if/else here, but I think it can be done better.
Second questions is. In the past I used CMSMSmle, it had a translation module where you could insert words with different translations and use the in your template.
How can this be done? For example, I want to insert something like this:
{$material}
which has 2 translations:
NL: Materiaal
EN: Material
Again, this can be done with if/else, but there must be a better way I think.
Thanks in advance!