One more approach to build a multilingual site with the regular CMSMS
Posted: Fri Feb 01, 2008 8:39 pm
[Update 1: If you start reading this thread here, note that the code has been improved several times since I posted this first message. You can read the messages in chronological order to follow the improvements or jump to the Final step by step implementation guide I posted at the end of the thread with the last version of the code.]
[Update 2/Actualización 2: He publicado una versión en español de la guía final de implementación. I've posted a Spanish version of the final implementation guide.]
[Update 3: You can see a working example of this method in a bilingual personal site of mine: http://alien.alinome.net.]
[Update 4 (2009-08-26): Pierre-Luc has written a module called Babel, based on this method, easier to use and with more features.]
[Update 5 (2009-09-12): After almost three years, I have decided not to use CMSMS any more. It has become too complex for my needs, version after version, and latest upgrades have broken many things in my code. I ported the site I mentioned in "Update 3" (and others) to a custom Website Revision System of my own, much simpler and productive (wiki-like, no database, straight PHP programming...). The website look is exactly the same than with CMSMS, though.]
Hi all,
Until now I have built only one site with CMSMS, monolingual; but some of my new projects will be in several languages. I could use other tools I used before, but I like CMSMS so much that I decided to give it a chance. I don't reject the idea to test the CMSMS MLE fork, but firstly I wanted to try to squeeze what I need out of the regular version.
I searched the forum for tips on this question and collected some interesting ideas, mainly about the same solution I already had in my mind at first: separated menu branches for the different languages. (By the way, it is really hard to find useful information with the provided search tool, so finally I googled with the "site:cmsmadesimple.org" option). All the approachs I found were (at first sight) too complex and hard to adapt one way or another. I wanted to find a solution as much simple and configurable as possible.
My goals were these:
Menu organization
I created two root section headers for the two languages needed. Their names are the language codes. All pages in every language are created in the corresponding branch:
Get the language of the current page
To have only one template for all pages I needed a Smarty variable to keep the current language and use it to select the corresponding parts of the template (the other way would be to use a different template for every language, what is worse to maintain).
My first provisional approach, just to be able to start programming the language bar, was a bit fooly: The user had to choose the language of every page by filling a content block. I put this at the top of the template:
Well, some minutes later :) the second approach was born and it was a bit -- smarty: As I told, the root parent of every page is a section header whose name is the language code itself. I just needed to get the name of the root parent of the page... But I didn't how to get it! I thought I could learn from the menu module code and program a kind of loop to navigate to the upper parent and so on. I found a possible solution too late in
the message Re: Using the Global variables from CMS. Too late, I said, because I had already thought a simpler solution:
The left char of the template var friendly_position was all I need! The new code at the top of the template was:
Of course the if structure is not very elegant. If more languages were needed, it would be better
to use an array with the languages codes, indexed by the menu root positions. Si I decided to write an User Defined Tag called {assign_page_lang}:
So finally this is the only code needed at the start of the template:
The template
The Smarty var $page_lang can now be used in the template and its blocks to select the content depending on the current language, e.g.:
Or:
Or the main menu itself, to show only the corresponding language branch:
Very simple.
The language menu
Now the language menu. I wrote a second UDT called... {language_menu}. In its code you'll see the simple trick to get every page related with its translation. Nevertheless I will explain that later.
The tag has two optional parameters:
Yes, it's very simple: just use the same page name (alias) for the language versions but add the language code to it, e.g.:
You create the language menu in your template like this:
Or with parameters:
CSS do the rest.
The code is working fine in a new personal site of mine I'm building these days. I'll post the URL later because the site has no content yet and looks really weird .
Future migration to CMSMS 2.0 or whatever
I use Mod Rewrite to get pretty URLs in the format "/contact_es.html" redirected to "/index.php?page=contact_es".
Maybe useful for someone, the lines I use for this in the file .htaccess are:
(Of course, you have to configure config.php too).
That way, if in the future I migrate to other multilingual system whith a different URL format I will easily create those fake HTML files with PHP code to redirect them (that would be easier for me than messing about Mod Rewrite). E.g. the file products_es.html would be:
Improvements
I'm not very used to PHP (the language I use the most is Forth, what is a extremely different thing!). If you find something that should be corrected or could be improved, please post. I suspect some if structures and conditions could be simpler (when I come back to PHP after a long time, I always forget about && and similar things ). The same about the relation beetwen PHP and Smarty, and the inner workings of CMSMS: all that is a new thing for me.
As the code comments explain, the page title option still does not work, but I will ask for help in the corresponding forum board.
Hope all this helps.
Cheers,
Marcos
PS: After implementing my solution I found this interesting message in the forum:
Starting homepage based on users browser language. I think I will add that feature in my site.
[Update 2/Actualización 2: He publicado una versión en español de la guía final de implementación. I've posted a Spanish version of the final implementation guide.]
[Update 3: You can see a working example of this method in a bilingual personal site of mine: http://alien.alinome.net.]
[Update 4 (2009-08-26): Pierre-Luc has written a module called Babel, based on this method, easier to use and with more features.]
[Update 5 (2009-09-12): After almost three years, I have decided not to use CMSMS any more. It has become too complex for my needs, version after version, and latest upgrades have broken many things in my code. I ported the site I mentioned in "Update 3" (and others) to a custom Website Revision System of my own, much simpler and productive (wiki-like, no database, straight PHP programming...). The website look is exactly the same than with CMSMS, though.]
Hi all,
Until now I have built only one site with CMSMS, monolingual; but some of my new projects will be in several languages. I could use other tools I used before, but I like CMSMS so much that I decided to give it a chance. I don't reject the idea to test the CMSMS MLE fork, but firstly I wanted to try to squeeze what I need out of the regular version.
I searched the forum for tips on this question and collected some interesting ideas, mainly about the same solution I already had in my mind at first: separated menu branches for the different languages. (By the way, it is really hard to find useful information with the provided search tool, so finally I googled with the "site:cmsmadesimple.org" option). All the approachs I found were (at first sight) too complex and hard to adapt one way or another. I wanted to find a solution as much simple and configurable as possible.
My goals were these:
- Only one page template for all pages in all languages (though of course you can use more if you like).
- The language bar buttons should link to the current page translation, not to the home page.
- Easily configurable for any number of languages.
- Easily configurable buttons in the language bar.
Menu organization
I created two root section headers for the two languages needed. Their names are the language codes. All pages in every language are created in the corresponding branch:
- es (Section Header)
- [li]page1
- page2
-
eo (Section Header)
- [li]page1
- page2
Get the language of the current page
To have only one template for all pages I needed a Smarty variable to keep the current language and use it to select the corresponding parts of the template (the other way would be to use a different template for every language, what is worse to maintain).
My first provisional approach, just to be able to start programming the language bar, was a bit fooly: The user had to choose the language of every page by filling a content block. I put this at the top of the template:
Code: Select all
{content block="Language code" oneline="true" assign="page_lang"}
{if $page_lang == ""} {assign var="page_lang" value="es"}{/if}
the message Re: Using the Global variables from CMS. Too late, I said, because I had already thought a simpler solution:
The left char of the template var friendly_position was all I need! The new code at the top of the template was:
Code: Select all
{if $friendly_position|truncate:1:"":true == "1"}
{assign var='page_lang' value='es'}
{else}
{assign var='page_lang' value='eo'}
{/if}
to use an array with the languages codes, indexed by the menu root positions. Si I decided to write an User Defined Tag called {assign_page_lang}:
Code: Select all
/*
Assign the Smarty variable page_lang depending on the menu branch the current page belongs to.
*/
global $gCms;
$language_codes = array(
"1" => "es",
"2" => "eo")
;
$menu_branch = substr($gCms->variables['friendly_position'],0,1);
$smarty = &$gCms->GetSmarty();
$smarty->assign('page_lang', $language_codes[$menu_branch]);
Code: Select all
{assign_page_lang}
The Smarty var $page_lang can now be used in the template and its blocks to select the content depending on the current language, e.g.:
Code: Select all
<__html xmlns='http://www.w3.org/1999/xhtml' xml:lang='{$page_lang}'>
Code: Select all
{if $page_lang == "es"}
<h1>Título en castellano</h1>
{else}
<h1>Esperanto-titolo</h1>
{/if}
Or the main menu itself, to show only the corresponding language branch:
Code: Select all
<div id='menu'>
{if $page_lang == "es"}{menu template='menu_1' start_element='1' number_of_levels='3'}
{else}{menu template='menu_1' start_element='2' number_of_levels='3'}
{/if}
</div>
The language menu
Now the language menu. I wrote a second UDT called... {language_menu}. In its code you'll see the simple trick to get every page related with its translation. Nevertheless I will explain that later.
The tag has two optional parameters:
- show_current = Show an inactive link to the current page in the menu? (true/false)
- show_title = Use the page titles instead of the defined buttons? (true/false)
Code: Select all
/*
Create a language menu.
2008-01-31 Por/Far/By Marcos Cruz (alinome.net)
*/
global $gCms;
/* Parameters
*/
$show_current = $params['show_current']; /* Show an inactive link to the current page in the menu? true/false */
$show_title = $params['show_title']; /* Use the page titles instead of the defined buttons? true/false */
/* Buttons to be used (if $show_title !== true). They could be images too.
I think the text "in language" in the language itself is the most accessible button.
(In my opinion flags are not a good option because they represent countries or regions, not languages).
*/
$language_buttons = array(
"es" => "<span xml:lang='es'>en castellano</span>",
"eo" => "<span xml:lang='eo'>en esperanto</span>")
;
$current_page = $gCms->variables['page_name'];
/* I explored how to get the page title and found this:
http://forum.cmsmadesimple.org/index.php/topic,1258.msg6831.html#msg6831 (too old)
http://forum.cmsmadesimple.org/index.php/topic,3778.msg93098.html#msg93098
But I haven't explored much yet because I don't use the show_title parameter now...
*/
$page_title = $gCms->variables['page_title']; /* ...so meanwhile this doesn't work :-) Someone can help? */
$page_lang = $gCms->smarty->get_template_vars('page_lang');
$base_page_name = ereg_replace("..$", "", $current_page ); /* take off the language code */
function smarty_cms_selflink($page,$text) {
/* Call Smarty {cms_selflink}
Doc found in:
http://wiki.cmsmadesimple.org/index.php/User_Handbook/Admin_Panel/Extensions/User_Defined_Tags#How_to_Execute_Smarty_Tags_from_A_User_Defined_Tag_.28UDT.29
*/
global $gCms;
if ( $text == "" ) { $smarty_data = "{cms_selflink page=" . $page . "}"; }
else { $smarty_data = '{cms_selflink page=' . $page . ' text="' . $text . '"}'; }
/*
echo "<p><strong>Debug info:</strong></p>";
echo "<p>page=$page</p>";
echo "<p>text=$text</p>";
echo "<p>smarty_data=$smarty_data</p>";
*/
$smarty = &$gCms->GetSmarty();
$smarty->_compile_source('temporary template', $smarty_data, $_compiled );
@ob_start();
$smarty->_eval('?>' . $_compiled);
$_contents = @ob_get_contents();
@ob_end_clean();
echo $_contents;
}
/* Create the menu
*/
/*
echo "<p><strong>Debug info:</strong><br/>";
echo "page_lang=$page_lang<br/>";
echo "show_title=$show_title<br/>";
echo "show_current=$show_current<br/>";
echo "base_page_name=$base_page_name<br/>";
echo "page_title=$page_title</p>";
*/
echo "<ul>";
foreach ($language_buttons as $language => $button ) {
if ($language != $page_lang) {
echo "<li>";
if ($show_title == true ) { smarty_cms_selflink($base_page_name.$language,""); }
else { smarty_cms_selflink($base_page_name.$language,$button); }
echo "</li>";
}
elseif ( $show_current == true ) {
echo "<li class='currentpage'>";
if ($show_title == true) { echo $page_title; } /* this doesn't work yet */
else { echo $button; }
echo "</li>";
}
}
echo "</ul>";
- es (Section Header)
- [li]contact_es
- products_es
-
eo (Section Header)
- [li]contact_eo
- products_eo
You create the language menu in your template like this:
Code: Select all
<div id='langmenu'>
{language_menu}
</div>
Code: Select all
<div id='langmenu'>
{language_menu show_current=true}
</div>
The code is working fine in a new personal site of mine I'm building these days. I'll post the URL later because the site has no content yet and looks really weird .
Future migration to CMSMS 2.0 or whatever
I use Mod Rewrite to get pretty URLs in the format "/contact_es.html" redirected to "/index.php?page=contact_es".
Maybe useful for someone, the lines I use for this in the file .htaccess are:
Code: Select all
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^([a-z]+[_]{1}[a-z]{2})\.html$ index.php?page=$1 [NC,L]
That way, if in the future I migrate to other multilingual system whith a different URL format I will easily create those fake HTML files with PHP code to redirect them (that would be easier for me than messing about Mod Rewrite). E.g. the file products_es.html would be:
Code: Select all
<?php
header( 'HTTP/1.1 301 Moved Permanently' );
header( 'Status: 301 Moved Permanently' );
header('Location: http://whateveritis.com/futuresystem/es/products/');
exit(0);
?>
I'm not very used to PHP (the language I use the most is Forth, what is a extremely different thing!). If you find something that should be corrected or could be improved, please post. I suspect some if structures and conditions could be simpler (when I come back to PHP after a long time, I always forget about && and similar things ). The same about the relation beetwen PHP and Smarty, and the inner workings of CMSMS: all that is a new thing for me.
As the code comments explain, the page title option still does not work, but I will ask for help in the corresponding forum board.
Hope all this helps.
Cheers,
Marcos
PS: After implementing my solution I found this interesting message in the forum:
Starting homepage based on users browser language. I think I will add that feature in my site.