12
\$\begingroup\$

I have just started to use PHP OOP and I would like to write a class to make a multi-language website. I started from this but I wanted to use OOP so I came up with this:

Language.php

<?php

class Language {

    private $UserLng;
    private $langSelected;
    public $lang = array();


    public function __construct($userLanguage){

        $this->UserLng = $userLanguage;
    }

    public function userLanguage(){

        switch($this->UserLng){
            /*
            ------------------
            Language: English
            ------------------
            */
            case "en":
                $lang['PAGE_TITLE'] = 'My website page title';
                $lang['HEADER_TITLE'] = 'My website header title';
                $lang['SITE_NAME'] = 'My Website';
                $lang['SLOGAN'] = 'My slogan here';
                $lang['HEADING'] = 'Heading';

                // Menu

                $lang['MENU_LOGIN'] = 'Login';
                $lang['MENU_SIGNUP'] = 'Sign up';
                $lang['MENU_FIND_RIDE'] = 'Find Ride';
                $lang['MENU_ADD_RIDE'] = 'Add Ride';
                $lang['MENU_LOGOUT'] = 'Logout';

                return $lang;
                break;

                /*
                ------------------
                Language: Italian
                ------------------
                */

            case "it":
                $lang['PAGE_TITLE'] = 'Il titolo della mia pagina';
                $lang['HEADER_TITLE'] = 'Il mio titolo';
                $lang['SITE_NAME'] = 'Il nome del mio sito';
                $lang['SLOGAN'] = 'Uno slogan';
                $lang['HEADING'] = 'Heading';

                // Menu

                $lang['MENU_LOGIN'] = 'Entra';
                $lang['MENU_SIGNUP'] = 'Registrati';
                $lang['MENU_FIND_RIDE'] = 'Trova gruppi';
                $lang['MENU_ADD_RIDE'] = 'Aggiungi gruppo';
                $lang['MENU_LOGOUT'] = 'Esci';

                return $lang;
                break;

                /*
                ------------------
                Default Language
                ------------------
                */
                default:
                $lang['PAGE_TITLE'] = 'My website page title';
                $lang['HEADER_TITLE'] = 'My website header title';
                $lang['SITE_NAME'] = 'My Website';
                $lang['SLOGAN'] = 'My slogan here';
                $lang['HEADING'] = 'Heading';

                // Menu

                $lang['MENU_LOGIN'] = 'Login';
                $lang['MENU_SIGNUP'] = 'Sign up';
                $lang['MENU_FIND_RIDE'] = 'Find Ride';
                $lang['MENU_ADD_RIDE'] = 'Add Ride';
                $lang['MENU_LOGOUT'] = 'Logout';

                return $lang;
                break;

        }
    }
}

index.php

<?php 
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);//Detecting Default Browser language
$language = New Language($lang);
$langArray = array();
$langArray =  $language->userLanguage();
?>

<div class="cssmenu">
    <ul>
        <li> class="active"><a href="/login"><?php echo $langArray['MENU_LOGIN']?></a></li>                                                                                         
        <li><a href="/rides"><?php echo $langArray['MENU_FIND_RIDE']?></a></li>                                               
        <li><a  id ="btnShow"><?php echo $langArray['MENU_ADD_RIDE']?></a></li>                                              
        <li><a href="/logout.php"><?php echo $langArray['MENU_LOGOUT']?> </a></li>                                                   
        <li><a href="/register"><?php echo $langArray['MENU_SIGNUP']?></a></li>
    </ul>
</div>

While this works perfectly, I am not sure this is the correct way to do it. I didn't find many tutorials on how to do this using OOP so I have the following doubts:

  1. Is this a correct way to do it?
  2. is this code maintainable?
  3. would it make more sense to create a table in the database with all the different languages?
  4. I am still struggling to understand "abstract classes". Would this be the case to create an abstract class language.php and then extend(I hope this is the correct terminology) that class with other languages class (english.php, italian.php etc).
\$\endgroup\$
9
  • \$\begingroup\$ this isn't really an answer, but Google will Translate your site into many common languages, you shouldn't waste your time reinventing this. I mean that anyone can navigate to a site in Italian and have Google Translate it for them. if you use Chrome it asks you if you want to translate the site if it detects the site in a language it is able to translate. \$\endgroup\$ – Malachi Jan 22 '14 at 14:50
  • 3
    \$\begingroup\$ @Malachi Defining your own translations is not reinventing, it's providing a decent translation. Humans are normally better than google at translating things. \$\endgroup\$ – Simon Forsberg Jan 22 '14 at 15:00
  • \$\begingroup\$ I would say that Google is pretty good at translation, but that Humans are not very good at spelling or grammar. but I see your point @SimonAndréForsberg \$\endgroup\$ – Malachi Jan 22 '14 at 15:13
  • 1
    \$\begingroup\$ @Malachi thank you for your comments. While I agree with you that humans are not very good at spelling or grammar, I found google translation quite funny sometimes. English is not my first language but I want to have full control over the way the website will be translated in other languages (English will be the default language). \$\endgroup\$ – Mat Jan 22 '14 at 15:49
  • 2
    \$\begingroup\$ I'm a stickler in terms of variable names. Keep whatever format you use the same so it's consistent across your code base. Having $UserLng and then other variables like $langSelected will start to become difficult to figure out both for yourself and other developers. EG: Make them all start lowercase. \$\endgroup\$ – Steven Leggett Jan 23 '14 at 12:38
10
\$\begingroup\$

You've made a good start, but the switch statement in userLanguage() just doesn't feel right from an Object orientated perspective:

A class called Language should represent a single language which is generic enough to fit all cases: however at the moment, whatever language it represents, it has the definitions of all of them just jammed into that one method.

Is it maintainable? Not really, no. At the moment you have two languages, which I assume you know. However, should you ever want to translate it into another language (perhaps using another Charset, you're going to run into issues of space in the file, saving the file correctly, and merging in translations provided by other people for your site.

As to whether to use databases, abstract classes et cetera: You may want to, though there are other solutions.

Solution 1: Simple Configuration File

parse_ini_file() is a very powerful little tool that does exactly what you expect. Using a simple file like this which we'll call en.ini:

PAGE_TITLE = My website page title
HEADER_TITLE = My website header title
SITE_NAME = My Website
SLOGAN = My slogan here
HEADING = Heading
MENU_LOGIN = Login
MENU_SIGNUP = Sign up
MENU_FIND_RIDE = Find Ride
MENU_ADD_RIDE = Add Ride
MENU_LOGOUT = Logout

You can simply use: parse_ini_file('en.ini') to return an array exactly as in your switch statement, which will be much easier for other (non-programmers) to read and write for you. And if you were to then continue naming the files with this style, you could reduce userLanguage() to something like:

public function userLanguage()
{
    $file = '/path/to/language/config/' . $this->UserLng . '.ini';
    if(!file_exists($file))
    {
        //Handle Error
    }
    return parse_ini_file($file);
}

Solution 2: Abstract class

As your array is basically acting as getter methods, an abstract Language class should have all the language components you need in it, like so:

interface Language
{
    public function getPageTitle();
    public function getHeaderTitle();
    public function getSiteName();
    public function getSlogan();
    public function getHeading();
    public function getMenuLogin();
    public function getMenuSignup();
    public function getMenuFindRide();
    public function getMenuAddRide();
    public function getMenuLogout();
}

Though the interface is small, implementing it may produce a big file, though it would arguably be clearer than the array style:

class English implements Language
{
    public function getHeaderTitle()
    {
        return 'My website header title';
    }

    public function getHeading()
    {
        return 'Heading';
    }

    // etc...
}

Alternative

Alternatively, you could combine these styles and have a singleton Language with getter methods accessing that array, i.e.:

class Language
{
    private $languageArray;
    private $userLanguage;

    public function __construct($language)
    {
        $this->userLanguage = $language;
        $this->languageArray = self::userLanguage();
    }

    private static function userLanguage()
    {
        $file = '/path/to/language/config/' . $this->userLanguage . '.ini';
        if(!file_exists($file))
        {
            //Handle Error
        }
        return parse_ini_file($file);
    }

    public function getPageTitle()
    {
        return $this->languageArray['PAGE_TITLE'];
    }

    public function getHeaderTitle()
    {
        return $this->languageArray['HEADER_TITLE'];
    }

    //etc...
}

Which will provide the benefits of both. Personally though, unless you're planning on adding more languages in the very near future, I believe solution #2 would suit you best.

\$\endgroup\$
0
7
\$\begingroup\$

I have done translation of one of my sites into English, French and German and what I found is that maintaining a class with getter's and setter's for a handful of phrases works, but anything more, just becomes hard work

I like @MrLore parse_ini_file() approach.

with a few differences

For example instead of using made up constants like PAGE_TITLE, use the real string in your native language.

So my en_nz.ini file looks like this

My website page title = My website page title

My fr_fr.ini file looks like this

My website page title = Mon site web page de titre

The advantages of the ini format style, as @MrLore said, you can give a file to someone (not a programmer) and they can do the translation for you.

Another advantage is if you use multiple programming languages, the ini file is independent of those.

You can also do more advanced stuff like including arguments into the strings

I am %d years old today = I am %d years old today

Then when you render the string in your page

<?php

class lang {

    private $lang = null;

    function __construct($lang) {
        $this->lang = parse_ini_file("{$lang}.ini");
    }


    public function xlate($str) {

        $arg_count = func_num_args();

        if ($arg_count > 1) {
            $params = func_get_args();

            // strip first arg
            array_shift($params);
        } else {
            $params = array();
        }

        $out_str = isset($this->lang[$str]) ? $this->lang[$str] : null;

        // if you string doesn't exist or is mistyped, then blow up, so we know about it
        // or you could even go away to google translate and perform the translation for
        // any missing strings
        if (!$out_str) {
            throw new exception("Lang String Not Found: $str");
        }

        return vsprintf($out_str, $params);
    }
}

$lang = new lang('fr_fr');

echo $lang->xlate('Thank you my friend');

echo $lang->xlate('My website page title for %d', 2014);

echo $lang->xlate('made up string');

fr_fr.ini

My website page title for %d = Mon site web page de titre pour %d
Thank you my friend = Merci mon ami

On a final note $lang->xlate may sound like a nice function name, but after a while you will get sick of typing it, $l->x is cryptic but shorter

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.