Welcome Guest, Not a member yet? Register   Sign In
Multilingual website solution with multilingual routes
#1

[eluser]Salvatore Formisano[/eluser]
Hi everyone,
during the last few weeks I've been working on a codeigniter site, and I wanted to have the chance to publish everything in both english and italian.

I found a few solutions, but none of them delivered what I wanted.

I therefore want to share what I've done so far, as I am aware there's a lot to improve.
Also, I'm fairly new to both codeigniter and php/back-end programming, so I look forward to hear your hopinions.

So, everything starts with a hook written by Phil Sturgeon:

Code:
<?
function pick_language() {

  require_once(APPPATH.'/config/language.php');

  session_start();

  // Lang set in URL via ?lang=something
  if(!empty($_GET['lang']))
  {
    // Turn en-gb into en
    $lang = substr($_GET['lang'], 0, 2);
    $_SESSION['lang_code'] = $lang;
  }

  // Lang has already been set and is stored in a session
  elseif( !empty($_SESSION['lang_code']))
  {
    $lang = $_SESSION['lang_code'];
  }

  // Lang has is picked by a user.
  // Set it to a session variable so we are only checking one place most of the time
  elseif( !empty($_COOKIE['lang_code']) )
  {
    $lang = $_SESSION['lang_code'] = $_COOKIE['lang_code'];
  }

  // Still no Lang. Lets try some browser detection then
  else if (!empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ))
  {
    // explode languages into array
    $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

    log_message('debug', 'Checking browser languages: '.implode(', ', $accept_langs));

    // Check them all, until we find a match
    foreach ($accept_langs as $lang)
    {
      // Turn en-gb into en
      $lang = substr($lang, 0, 2);

      // Check its in the array. If so, break the loop, we have one!
      if(in_array($lang, array_keys($config['supported_languages'])))
      {
        break;
      }
    }
  }

  // If no language has been worked out - or it is not supported - use the default
  if(empty($lang) or !in_array($lang, array_keys($config['supported_languages'])))
  {
    $lang = $config['default_language'];
  }

  // Whatever we decided the lang was, save it for next time to avoid working it out again
  $_SESSION['lang_code'] = $lang;

  // Load CI config class
  $CI_config =& load_class('Config');

  // Set the language config. Selects the folder name from its key of 'en'
  $CI_config->set_item('language', $config['supported_languages'][$lang]['folder']);

  // Sets a constant to use throughout ALL of CI.
  define('CURRENT_LANGUAGE', $lang);
}

?>

This will set the CURRENT_LANGUAGE constant which will hold the language we want to display the site in.

To have multilingual routes, I created a new directory called 'routes' under the application directory (application/routes).

This directory holds (in my configuration) 2 files:
routes_en.php and routes_it.php

All I do in these files is defining a bunch of constants. Here's a chunk of routes_en.php

Code:
/* users */
define('ADMIN_SHOW_USERS_PATH','en/admin/users');
define('ADMIN_NEW_USER_PATH','en/admin/users/new');
define('ADMIN_CREATE_USER_PATH','en/admin/users/create');
define('ADMIN_EDIT_USER_PATH','en/admin/users/edit/:num');
define('ADMIN_UPDATE_USER_PATH','en/admin/users/update');
define('ADMIN_DELETE_USER_PATH','en/admin/users/delete/:num');

and here's the same piece in routes_it.php

Code:
/* utenti */
define('ADMIN_SHOW_USERS_PATH','it/amministrazione/utenti');
define('ADMIN_NEW_USER_PATH','it/amministrazione/utenti/aggiungi');
define('ADMIN_CREATE_USER_PATH','it/amministrazione/utenti/salva');
define('ADMIN_EDIT_USER_PATH','it/amministrazione/utenti/modifica/:num');
define('ADMIN_UPDATE_USER_PATH','it/amministrazione/utenti/salva_modifiche');
define('ADMIN_DELETE_USER_PATH','it/amministrazione/utenti/elimina/:num');

The routes will obviously still need to be declared in application/config/routes.php as all I did so far is defining a few constants.

this is a piece of my routes.php that makes use of the constants I published:

Code:
require_once(APPPATH.'/routes/routes_'.CURRENT_LANGUAGE.'.php');

/* users */
$route[ADMIN_SHOW_USERS_PATH]="admin/users_controller/show_users";
$route[ADMIN_NEW_USER_PATH]="admin/users_controller/new_user";
$route[ADMIN_CREATE_USER_PATH]="admin/users_controller/create_user";
$route[ADMIN_EDIT_USER_PATH]="admin/users_controller/edit_user";
$route[ADMIN_UPDATE_USER_PATH]="admin/users_controller/update_user";
$route[ADMIN_DELETE_USER_PATH]="admin/users_controller/delete_user";

as you can see I'm including the routes file with the declared constants in the language I need, and I do so by using the CURRENT_LANGUAGE constant.


Now, how to call these routes from the view files?

I made a helper called routes_helper.php :

Code:
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');


if ( ! function_exists('get_route'))
{

    function get_route($route,$parameter=NULL)
    {
      $string = "walawalabingbang";
      $any = ":any";
      $num = ":num";
      if(strpos($route,$any)!==false){
        if($parameter==NULL) return "You need to specify a parameter for this route";
        $route = str_replace($any,$parameter,$route);
      }
      if(strpos($route,$num)!==false){
        if($parameter==NULL) return "You need to specify a parameter for this route";
        $route = str_replace($num,$parameter,$route);
      }
      return $route;
    }

}

finally, with this setup this is how a link looks in a view:

Code:
&lt;?=anchor(get_route(ADMIN_SHOW_USERS_PATH),"<span>".$this->lang->line('users_list')."</span>")?&gt;

and if you need to pass any type of data trough the uri:

Code:
&lt;?= anchor(
      get_route(ADMIN_DELETE_USER_PATH,$user->id),
      $this->lang->line('app_delete'),
      array("class"=>"ico ico-delete delete delete_user")
    ); ?&gt;
#2

[eluser]Salvatore Formisano[/eluser]
So...

the good part of this is that you get completely separated and translated routes for different languages, which is good both for the user (who gets meaningful urls in his language) and for seo (meaningful urls AND different urls for different content are always good)

the bad part is that this complicates things a bit, as you need to mantain the routes files just as you would need to do for normal language files.

Any kind of text content in my database schema has an italian and an english version.

For instance, my site_contents table has:

id,sc_key,title_it,title_en,content_it,content_en

My categories table has:

id,cat_name_en,cat_name_it,cat_permalink_en,cat_permalink_it


because of this I can easily take advantage of the CURRENT_LANGUAGE constant and make sure even database content and database built urls are being retrieved in the appropriate language.

A real case scenario:

I want to display the blog posts contained in the "News and Politics" category


My route for the blog category archive in routes.php would be

Code:
$route[BLOG_CATEGORY_ARCHIVE_PATH]="blog_controller/show_category_posts";

the BLOG_CATEGORY_ARCHIVE_PATH would be defined in routes_en.php as

Code:
define('BLOG_CATEGORY_ARCHIVE_PATH','en/blog/category_archive/:any');

and in routes_it.php as

Code:
define('BLOG_CATEGORY_ARCHIVE_PATH','it/blog/archivio_categoria/:any');

(urls are just for demo purpose, I'm sure anyone could come up with nicer ones Big Grin )

SO, I used :any for this route because I want to use a permalink rather than an id.

Let's take a look at the "News and Politics" category record in the categories table:

Code:
======================================================================================
id | cat_name_en       | cat_name_it        | cat_permalink_en  | cat_permalink_it  
======================================================================================
22 | News and Politics | Notizie e Politica | news-and-politics | notizie-e-politica
--------------------------------------------------------------------------------------

Now, in this situation the real url an English user would have is:

http://www.sitename.com/en/blog/category...d-politics


The route will point to the blog_controller and launch the show_category_posts method.

From here on it really doesn't need any explanation: since we have the useful CURRENT_LANGUAGE constant, we can use this to let the controller look into the english columns and return the english content.

So:

1] Get the category where 'cat_permalink_en' is equal to 'news-and-politics'
2] The record is found and has an id of 22, find all the blog_posts where cat_id is 22

(for clarity this could be the blog_posts structure)

Code:
========================================================================================
id | cat_id | post_title_en | post_title_it    | post_permalink_en | post_permalink_it
========================================================================================
6  | 22     | A simple post | Un semplice post | a-simple-post     | un-semplice-post
----------------------------------------------------------------------------------------

(RECORD CONTINUES..)
========================================================================================
post_content_en              | post_content_it        |
========================================================================================
this morning the president.. | questa mattina il pr.. |

3] From here on it's the same, show all the _en contents, use the _en permalinks



this is as much as I can write at 03:44 Big Grin


I am aware that the system is far from being perfect (or even good probably) - but this is exactly why I am posting here, I want to know what you people think of it, and if you have any practical ideas and proposals to build on it and make it quicker and easier to setup and mantain.



p.s. This will probably give me away for the CI noob that I am, but.. in that pick_language hook I pasted before.. could I make use of the URI helper somehow?

It would be really awesome to turn this:

Code:
// Lang set in URL via ?lang=something
  if(!empty($_GET['lang']))
  {
    // Turn en-gb into en
    $lang = substr($_GET['lang'], 0, 2);
    $_SESSION['lang_code'] = $lang;
  }

into something like this:

Code:
// Lang set in URL via ?lang=something
  if($this->uri->segment(1)=='en' || $this->uri->segment(1)=='it')
  {
    // Turn en-gb into en
    $lang = $this->uri->segment(1);
    $_SESSION['lang_code'] = $lang;
  }

of course I would be aiming at having it more dinamic, probably checking is the value of segment(1) exists as key of an array of available languages.



Thanks to all and thanks to EllisLab for this wonderful framework!
#3

[eluser]Salvatore Formisano[/eluser]
mmh, I'm guessing the idea isn't finding many fans? Smile
#4

[eluser]Ngulo[/eluser]
eeheh mannaggia... tu hai risolto?

io sto cercando di fare la route delle url switchando i file di lingua ma non riesco...

uff
#5

[eluser]Salvatore Formisano[/eluser]
Hi,

yes, I did find a working solution- if you want I can write what I did here, but in a couple of days (you know, it's Christmas Big Grin )


And by the way, I don't think it's appropriate to discuss anything in a language other than English here.


So, let me know if you're interested


Cheers
#6

[eluser]Ngulo[/eluser]
Hi, sorry, you're right..it's hard to me but it's better to talk in english language Smile

I'm really interested in your solution about routing and really could be great to get some suggestion from you about Smile

really thanks for helping me.

i know it's Christmas so no problem Smile have nice holidays Smile

best regards
#7

[eluser]millo[/eluser]
Hi Salvatore,

Strange that you didn't get so much response on this. I would have thought multilingual routes would have been and interesting topic since there doesn't seem to be much around addressing this. I'm currently looking around also. I've seen this http://ellislab.com/forums/viewthread/84593/P105/ but the URL doesn't change.
#8

[eluser]umefarooq[/eluser]
check this thread hope will help

http://ellislab.com/forums/viewthread/180320/




Theme © iAndrew 2016 - Forum software by © MyBB