Welcome Guest, Not a member yet? Register   Sign In
Multilanguage Support Needed
#1

[eluser]n1c0[/eluser]
Hello everybody, I've spent some days now wondering about how to build a multilingual website using - as always - as less redundant code as possible. I took a deep look to every strategy that it has come to my mind: hooks, helpers, parser and the standard approach too, but in the end I saw no real solution to my problem.

I would like to give the possibility to the users of my (future) website to translate it in their mother language, so I was thinking to associate a language file to every view of the website, and then automatically have "something" to check if the page exists in the user preferred language (witch will be stored in a session variable): if not - obviously - use the website standard language. That's not all: language files may be sometimes not fully complete, due to interface updates or things like that, so I would like also that "something" to check if in the user language file all the variables needed to the view are defined: if not take the equivalent from the default language file.

The only - maybe - really working solution witch come to my mind would be that to use a huge three dimensional matrix to store all the strings into a single file (or into a database), but that seems to be a pretty lame strategy, and doesn't take advantage of CI at all, witch just doubles it lameness Sad

Any workflow suggestion (and even code of course Big Grin) would be very appreciated.

Thank you for your time,
Nico
#2

[eluser]adamfairholm[/eluser]
Nico,

I think it would be unnecessary to check to see if all the language elements are loaded for the view. My approach would be to change up the 2 functions of the core Language library.

The first thing to do would be to change the load language file function. Right now it basically loads the array, and then merges that language file array into the master language array. In CI 2.0, it happens on line 110:

Code:
$this->language = array_merge($this->language, $lang);

You could extend the Lang.php core library to put that into a multi-dimensional array for the language. So it could change to:

Code:
$this->language[$idiom] = array_merge($this->language[$idiom], $lang);

Then, change the function to load the default language as well, and put that into another node on the class language array. A check to see if the default language and the user language would be good.

There are other considerations in that function, but that's the main change I think.

Then you've got the "line" function. Right now, it doesn't return an error if there is no language array node that you are looking for. It just returns false. You could change it to have a few different levels of fallback:

- If the line comes back as false, meaning that line doesn't exist, then check the default language.

- If it isn't loaded, load that node then.

Yes it means double language loading, but right now that's all I can come up with. Of course, putting the new functions in a MY_Lang.php file would be a better route than changing the core files.

For the language choosing though, have you thought of going with a language subdomain? Like en.wikipedia.org.

Adam
#3

[eluser]n1c0[/eluser]
Thank you for the fast and accurate reply Adam!
Your solution seems to really fit well my scenario!

Now only one thing is left unresolved: I would like to activate a 'display_override' hook to have each page to automatically load and parse the corresponding words from the language dictionary, so basically I need to know what views have been loaded: should I edit the load function to store the loaded views (path) into a global variable? And in this case what would be the best one to be used as a container?

BTW: I'm not really familiar with matrix usage on classes. Do you know why it gives me an 'undefined index' error on the line witch we have modified on the load function? I thought that being the language attribute already set to be an array it would have taken that line of code without further declarations..

Your help is very much appreciated,
many thanks,
Nico
#4

[eluser]adamfairholm[/eluser]
Nico,

If you extended the view function of the Loader, you can save the name of the view into an array of views that you've loaded. Would that solve that problem? I'm still unclear as to why the hook is necessary, but you know your site better than I do! I've always just put in <?=$this->lang->line('');?> calls for lines in shared views for different languages.

Just remember to extend the Language class and the Loader class to add in that functionality - otherwise when you want to upgrade CI you're screwed.

It might be throwing up the error because the first time it does the array merge, we haven't defined $this->language[$idiom]. I guess you could do a check for that before that line like:

Code:
if( ! isset($this->language[$idiom]) )
     $this->language[$idiom] = array();

This is all untested, but that might work as a solution.

Adam
#5

[eluser]n1c0[/eluser]
Works perfectly! Hurray!
Thank you very much Adam!

About the second part of my multilingual implementation strategy: I thought to have every page automatically parsed by the CI template class to spare me all the initialization and function-calling code that I would have to repeat on each controller and view, allowing me to simply write {variable_name} in the view without further work.
I know that it's gonna be a heavier code in terms of resource needed, but the thing does not bother me. It's gonna be a quite light website anyway.
Do you find it not reasonable?

Glory to you my helpful friend! Smile

Nico
#6

[eluser]adamfairholm[/eluser]
Sweet - glad it works!

Not unreasonable, I just hadn't thought of doing it that way. I haven't worked on a multi-lingual site yet so I haven't had to deal with the kinds of issues it presents. That approach does seem to clear things up on the view side. I really hate having all of those $this->lang->line('') calls in views - it clutters it up pretty quick.
#7

[eluser]n1c0[/eluser]
So, yesterday I tried to make our solution working, but the CI parser class (and to tell the truth our language class too) didn't seem to be of any help in the end. Things just kept getting more and more complicated, with absolutely no satisfying results in the end.
So I just took what the code teached me in the last few days and put it together into a solution that would fit me the best.

First of all as you suggested I made a MY_Loader class to extend the native one, here's the code:
Code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class MY_Loader extends CI_Loader {
    
    var $loaded_views = array();

    function view($view, $vars = array(), $return = FALSE)
    {
        // We add the functionality to store the loaded views into an array
        $this->loaded_views[] = $view;
        return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
    }
    
    function get_loaded_views()
    {
        return $this->loaded_views;
    }
}

And then I just made a 'display_override' hook to take care of the rest. Here's it's code code:
Code:
<?php

class Multilanguage extends Controller {
    
    function multilanguage_support()
    {
        // Get the lists of called views: each view should have an equivalent _lang file,
        // if it doesn't anyway no errors will be generated
        $loaded_views = $this->load->get_loaded_views();
        
        if (count($loaded_views) > 0)
        {
            // Get the default and the user language
            $CI =& get_instance();
            $deft_lang = $CI->config->item('language');
            $user_lang = 'italian';

            // It's important to have the default language loaded before the user language,
            // so that the second will overwrite the first (if the elements are present)
            $supported_languages = array($deft_lang);
            if ($user_lang != $deft_lang)
            {
                $supported_languages[] = $user_lang;
            }
            
            foreach ($supported_languages as $supported_language)
            {
                foreach ($loaded_views as $loaded_view)
                {
                    if (file_exists(APPPATH.'language/'.$supported_language.'/'.str_replace('_view', '_lang', $loaded_view).EXT))
                    {
                        include(APPPATH.'language/'.$supported_language.'/'.str_replace('_view', '_lang', $loaded_view).EXT);
                    }
                }
            }
            
            // Now that we have the $lang array fully populated we can change each words presented in the view
            // as '{array_index_identifier}' with it's equivalent human readable value
            $view_words = array();
            $human_words = array();
            
            foreach ($lang as $view_word => $human_word)
            {
                $view_words[] = '{'.$view_word.'}';
                $human_words[] = $human_word;
            }
                
            echo str_replace($view_words, $human_words, $CI->output->get_output());
            
            return;
        }
        
        // We'll output the simple code if no views were loaded
        echo $CI->output->get_output();
        return;
    }
}

/* End of file multilanguage.php */
/* Location: ./system/application/hooks/multilanguage.php */

It works quite well, and I feel quite satisfied with it.
What do you think about it? Any suggestion it's gonna be as always very appreciated. Big Grin

Nico




Theme © iAndrew 2016 - Forum software by © MyBB