28

Questions Updated instead of making a new question...

I really want to provide a few alternative languages other then English on my social network site I am building, this will be my first time doing any kind of language translation so please bear with me.
I am researching so I am al ear and open to ideas and I have a lot already here is are the questions.

1)
What does i18n mean, I see it often when researching language translation on SO?

2)
Most people say use gettext PHP has an extension or support for it,
well I have been researching it and I have a basic understanding of it, as far as I can tell it is a lot of extra work to go this route,
I mean coding my site to use it's functions ie; _('hello world i'm in English for now') or else gettext('hello world i'm in English for now') is no problem as any route I go will require that.
But then you have to install gettext on your server and get it working,
then use some special editors to create special files and compile them I think?

Sounds like a pain, I understand this is supposed to be the best route to go though, well everyone seems to say it is.
So can someone tell me why this is the route to go?

3)
I really like the simplicity of this approach, just building a language array and calling the phrase you need in a function like the example below , you would then just include a file with the appropriate language array.

What I really want to know is, would this be the less better performance method on a high traffic and fairly large site compared to using gettext and if so can you explain why please?

<?PHP
//Have seperate language files for each language I add, this would be english file
function lang($phrase){
    static $lang = array(
        'NO_PHOTO' => 'No photo\'s available',
        'NEW_MEMBER' => 'This user is new'
    );
    return $lang[$phrase];
}
//Then in application where there is text from the site and not from users I would do something like this
echo lang('NO_PHOTO');  // No photo's available would show here
?>

* some code used from brianreavis's answer below

JasonDavis
  • 48,204
  • 100
  • 318
  • 537
  • You know that you don't have to use the _ in an array, but yes, you should probably use the php extensions rather than roll your own. – SeanJA Sep 12 '09 at 03:21

9 Answers9

25

It'd probably be best to define a function that handles your language mapping. That way, if you do want to change how it works later, you're not forced to scour hundreds of scripts for cases where you used $lang[...] and replace them with something else.

Something like this would work and would be nice & fast:

function lang($phrase){
    static $lang = array(
        'NO_PHOTO' => 'No photo\'s available',
        'NEW_MEMBER' => 'This user is new'
    );
    return $lang[$phrase];
}

Make sure the array is declared static inside the function so it doesn't get reallocated each time the function is called. This is especially important when $lang is really large.

To use it:

echo lang('NO_PHOTO');

For handling multiple languages, just have this function defined in multiple files (like en.php, fr.php, etc) and require() the appropriate one for the user.

brianreavis
  • 11,562
  • 3
  • 43
  • 50
  • 3
    FYI, this solution is inferior to Fragsworth's suggestion: `gettext`. Gettext is definitely the better route for this sort of thing. However, this is how you'd do it if you were to roll your own basic system. – brianreavis Sep 12 '09 at 03:10
  • Hi, I like the solution! I come from C# and I have a doubt of how static variables work in PHP. When you declare a static variable in C#, for example an array, this it's kept in memory and you only need to load it one time and it's value persist between different user threads. But in PHP it loses the value in each execution as far as I know, and if you want to keep things in memory you have to use alternatives like APC. Why then @brianreavis you recommend to make it static to not "reallocate" each time? Thank you! – Roberto Zamora Sep 02 '14 at 11:08
  • @RobertoZamora Right – I only meant it didn't need to be reallocated for each call. PHP execution lives and dies with each request. To persist anything for longer, you'll have to look into APC (or similar). – brianreavis Sep 02 '14 at 20:08
  • i get an error when i use this $day1Txt = Day_1_Offer('EMAIL_OFFER'); – Amesey Dec 23 '14 at 09:51
11

This might work better:

function _L($phrase){
static $_L = array(
    'NO_PHOTO' => 'No photo\'s available',
    'NEW_MEMBER' => 'This user is new'
);

     return (!array_key_exists($phrase,$_L)) ? $phrase : $_L[$phrase];
}

Thats what i use for now. If the language is not found, it will return the phrase, instead of an error.

You should note that an array can contain no more than ~65500 items. Should be enough but well, just saying.

Here's some code that i use to check for the user's language:

<?php
function setSessionLanguageToDefault() {
    $ip=$_SERVER['REMOTE_ADDR'];
    $url='http://api.hostip.info/get_html.php?ip='.$ip;
    $data=file_get_contents($url);
    $s=explode (':',$data);
    $s2=explode('(',$s[1]);

    $country=str_replace(')','',substr($s2[1], 0, 3));

    if ($country=='us') {
        $country='en';
    }

    $country=strtolower(ereg_replace("[^A-Za-z0-9]", "", $country ));
    $_SESSION["_LANGUAGE"]=$country;
}

if (!isset($_SESSION["_LANGUAGE"])) {
    setSessionLanguageToDefault();
}

if (file_exists(APP_DIR.'/language/'.$_SESSION["_LANGUAGE"].'.php')) {
    include(APP_DIR.'/language/'.$_SESSION["_LANGUAGE"].'.php');
} else {
    include(APP_DIR.'/language/'.DEFAULT_LANG.'.php');
}

?>

Its not done yet, but well i think this might help a lot.

Rob Quist
  • 111
  • 1
  • 3
7

Don't write your own language framework. Use gettext. PHP has standard bindings that you can install.

Fragsworth
  • 33,919
  • 27
  • 84
  • 97
  • I just glanced over the gettext page earliar today before I posted this, I cannot really make out how it works in a nutshell can you explain a little about it? – JasonDavis Sep 12 '09 at 03:27
  • jasondavis: it does exactly what you are attempting to do and more. When you install it, you choose somewhere to place your language translations file (".po"). You can translate all your strings in this file. Then in your source, you wrap all your multilingual strings with gettext() (or typically an alias, '_()') and it will pull your current translation of that string for you. It also provides for plural phrases and some other important features that you will find necessary. – Fragsworth Sep 12 '09 at 06:12
  • 2
    @future reader, I will say, if your website or app doesn't use hundreds to thousands of words, going the associative array way is better than gettext. Gettext is extremely outdated in its implementations and can be a nightmare to debug. I just spent three days trying to get it to work on my server. It has many issues and is not well maintained. Trust me you will run into some of its issues. Better off using the php-gettext (gettext emulator), some other implementations or writing your own library. – Emmanuel N K Aug 17 '16 at 20:30
  • @EmmanuelNK I agree. gettext is broken. Plus, it requires some special files. Plus, recompiling a .po file sometimes requires to restart the service. – magallanes Dec 17 '17 at 21:19
4

Don't reinvent the wheel. Use for example gettext or Zend_Translate.

raspi
  • 5,962
  • 3
  • 34
  • 51
  • The Zend_Translate link is stale, for the v1 framework use [this link](http://framework.zend.com/manual/1.12/en/zend.translate.html) & for the v2 framework use [this one](http://framework.zend.com/manual/current/en/modules/zend.i18n.translating.html). – Noob-o-tron 5000 Feb 17 '16 at 11:33
  • At least for windows, gettext is still so bad broken. – magallanes Dec 17 '17 at 21:17
2

As the other answers don't really answer all the questions, I will go for that in my answer plus offering a sensible alternative.

1) I18n is short for Internationalization and has some similarities to I-eighteen-n.

2) In my honest opinion gettext is a waste of time.

3) Your approach looks good. What you should look for are language variables. The WoltLab Community Framework 2.0 implements a two-way language system. For once there are language variables that are saved in database and inside a template one only uses the name of the variable which will then be replaced with the content of the variable in the current language (if available). The second part of the system provides a way to save user generated content in multiple languages (input in multiple languages required).

Basically you have the interface text that is defined by the developer and the content that is defined by the user. The multilingual text of the content is saved in language variables and the name of the language variable is then used as value for the text field in the specific content table (as single-language contents are also possible).

The structure of the WCF is sadly in a way that reusing code outside of the framework is very difficult but you can use it as inspiration. The scope of the system depends solely on what you want to achieve with your site. If it is going to be big than you should definitely take a look at the WCF system. If it's small a few dedicated language files (de.php, en.php, etc), from which the correct one for the current language is included, will do.

Jim Martens
  • 349
  • 1
  • 4
  • 13
1

why not you just make it as multi-dimesional array...such as this

<?php

$lang = array(
    'EN'=> array(
        'NO_PHOTO'=>'No photo\'s avaiable',
        'NEW_MEMBER'=>'This user is new',
    ),
    'MY'=> array(
        'NO_PHOTO'=>'Tiada gambar',
        'NEW_MEMBER'=>'Ini adalah pengguna baru',
    )
);

?>
user149513
  • 1,732
  • 2
  • 11
  • 11
  • 13
    This'd use more memory than necessary---because this'd be loading *every* language for *every* request. It'd be better to break it into multiple files and load the single appropriate one. – brianreavis Sep 12 '09 at 03:14
1

You can do this:

class T {
const language = "English";
const home = "Home";
const blog = "Blog";
const forum = "Forum";
const contact = "Support";
}

You would have a file like this for each language. To use the text:

There is no place like <?=T::home?>.

The downside is that if you add a new constant, you have to do it for every langauge file. If you forget one, your page breaks for that language. That is a bit nasty, but it is efficient since it doesn't need to create a large associative array and possibly the values even get inlined.

Maybe access could be improved, eg:

class T {
    const home = "home";

    public static function _ ($name) {
        $value = @constant("self::$name");
        return $value ? $value : $name;
    }

    // Or maybe through an instance:
    public function __get ($name) {
        $value = @constant("self::$name");
        return $value ? $value : $name;
    }
}
echo "There is no " . T::_("place") . " like " . T::_("home");
$T = new T();
echo "There is no " . $T->place . " like " . $T->home;

We still avoid the array and rely on constant to do the lookup, which I assume is more expensive than using the constants directly. The up side is the lookup can use a fallback when the key is not found.

NateS
  • 5,751
  • 4
  • 49
  • 59
1

An extension to the answers above whom deserve the credit - I'm just posting as maybe this will also be useful to someone else who ends up here.

I personally prefer the key to the array to be the actual phrase in my mother tongue (in my case English) rather than a CONSTANT_VALUE because:

  • I find it easier to read the code when the text is in my native language rather than having to remember what a CONSTANT_VALUE actually outputs
  • It means no lookup is needed to return the phrase for visitors who also use my naitive language (giving marginally better performance)
  • It's one less list of values to maintain

The downside is that it's harder to spot missing values in other languages as you don't necessarily have a master list anymore - I also log a warning from the abstract method so that I spot any missing values.

I implemented as:

  • An abstract class with static methods for outputting the text value (using late static binding)
  • A concrete class for each language: English overriding the method to return the phrase without translation, other languages overriding the list of phrases so that a translated phrase is returned
    <?php
    namespace Language;

    abstract class _Language
    {
        protected static $displayText = array();

        public static function output($phrase){
            return static::$displayText[$phrase] ?? $phrase;
        }
    }
    <?php
    namespace Language;

    class English extends _Language
    {
        public static function output($phrase){
            return $phrase;
        }
    }
    <?php
    namespace Language;

    class Spanish extends _Language
    {
        protected static $displayText = array(
            'Forename' => 'Nombre',
            'Registered Email' => 'Correo electrónico registrado',
            'Surname' => 'Apellido'
        );
    }

Usage:

    $language = new \Language\Spanish();
    echo $language::output('Forename'); // Outputs: Nombre
    $language = new \Language\English();
    echo $language::output('Registered Email'); // Outputs: Registered Email
Steve
  • 11
  • 1
0

Unfortunately gettext not work good and have problems in various situation like on different OS (Windows or Linux) and make it work is very difficult.

In addition it require you set lot's of environment variables and domains and this not have any sense.

If a developer want simply get the translation of a text he should only set the .mo file path and get the translation with one function like translate("hello","en_EN"); With gettext this is not possible.