Classes to make Smarty templates multilanguage

Author: André Rabold

The following class subclasses smarty to add multilanguage support

Last update: April, 30. 2003

Updated : Decembre, 26. 2003: See post http://www.phpinsider.com/smarty-forum/viewtopic.php?p=6859#6859

Updated : January, 29. 2004: Multiline values, corrections and optimizations

<?php
  require_once ("Smarty.class.php");

  /**
   * smarty_prefilter_i18n()
   * This function takes the language file, and rips it into the template
   * $GLOBALS['_NG_LANGUAGE_'] is not unset anymore
   *
   * @param $tpl_source
   * @return
   **/
  function smarty_prefilter_i18n($tpl_source, &$smarty) {
    if (!is_object($GLOBALS['_NG_LANGUAGE_'])) {
      die("Error loading Multilanguage Support");
    }
    // load translations (if needed)
    $GLOBALS['_NG_LANGUAGE_']->loadCurrentTranslationTable();
    // Now replace the matched language strings with the entry in the file
    return preg_replace_callback('/##(.+?)##/', '_compile_lang', $tpl_source);
  }

  /**
   * _compile_lang
   * Called by smarty_prefilter_i18n function it processes every language
   * identifier, and inserts the language string in its place.
   *
   */
  function _compile_lang($key) {
    return $GLOBALS['_NG_LANGUAGE_']->getTranslation($key[1]);
  }


  class smartyML extends Smarty {
    var $language;

    function smartyML ($locale="") {
      $this->Smarty();

      // Multilanguage Support
      // use $smarty->language->setLocale() to change the language of your template
      //     $smarty->loadTranslationTable() to load custom translation tables
      $this->language = new ngLanguage($locale); // create a new language object
      $GLOBALS['_NG_LANGUAGE_'] =& $this->language;
      $this->register_prefilter("smarty_prefilter_i18n");
    }

    function fetch($_smarty_tpl_file, $_smarty_cache_id = null, $_smarty_compile_id = null, $_smarty_display = false) {
      // We need to set the cache id and the compile id so a new script will be
      // compiled for each language. This makes things really fast ;-)
      $_smarty_compile_id = $this->language->getCurrentLanguage().'-'.$_smarty_compile_id;
      $_smarty_cache_id = $_smarty_compile_id;

      // Now call parent method
      return parent::fetch( $_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $_smarty_display );
    }

  /**
   * test to see if valid cache exists for this template
   *
   * @param string $tpl_file name of template file
   * @param string $cache_id
   * @param string $compile_id
   * @return string|false results of {@link _read_cache_file()}
   */
   function is_cached($tpl_file, $cache_id = null, $compile_id = null)
   {
            if (!$this->caching)
                     return false;
   
            if (!isset($compile_id)) {
                     $compile_id = $this->language->getCurrentLanguage().'-'.$this->compile_id;
                     $cache_id = $compile_id;
            }
   
            return parent::is_cached($tpl_file, $cache_id, $compile_id);
   }

  }
  class ngLanguage {
    var $_translationTable;        // currently loaded translation table
    var $_supportedLanguages;      // array of all supported languages
    var $_defaultLocale;           // the default language
    var $_currentLocale;           // currently set locale
    var $_currentLanguage;         // currently loaded language
    var $_languageTable;           // array of language to file associations
    var $_loadedTranslationTables; // array of all loaded translation tables

    function ngLanguage($locale="") {
      $this->_languageTable = Array(
        "de" => "deu",
        "en" => "eng",
        "en-us" => "eng",
        "en-gb" => "eng",
        "nl" => "nld",
        "zh" => "chn",
        "dk" => "dnk",
        "es" => "esp",
        "fr" => "fra",
        "it" => "ita",
        "no" => "nor",
        "pl" => "pol",
        "pt" => "prt",
        "ru" => "rus",
        "sv" => "swe",
        "tr" => "tur"
      ); // to be continued ...
      $this->_translationTable = Array();
      $this->_loadedTranslationTables = Array();
      foreach ($this->_languageTable as $lang)
        $this->_translationTable[$lang] = Array();

      $this->_defaultLocale = 'en';
      if (empty($locale))
        $locale = $this->getHTTPAcceptLanguage();
      $this->setCurrentLocale($locale);
    }

    function getAvailableLocales() {
      return array_keys($this->_languageTable);
    }

    function getAvailableLanguages() {
      return array_unique(array_values($this->_languageTable));
    }

    function getCurrentLanguage() {
      return $this->_currentLanguage;
    }

    function setCurrentLanguage($language) {
      $this->_currentLanguage = $language;
    }

    function getCurrentLocale() {
      return $this->_currentLocale;
    }

    function setCurrentLocale($locale) {
      $language = $this->_languageTable[$locale];
      if (empty($language)) {
        die ("LANGUAGE Error: Unsupported locale '$locale'");
      }
      $this->_currentLocale = $locale;
      return $this->setCurrentLanguage($language);
    }

    function getDefaultLocale() {
      return $this->_defaultLocale;
    }

    function getHTTPAcceptLanguage() {
      $langs = explode(';', $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
      $locales = $this->getAvailableLocales();
      foreach ($langs as $value_and_quality) {
          // Loop through all the languages, to see if any match our supported ones
          $values = explode(',', $value_and_quality);
          foreach ($values as $value) {
            if (in_array($value, $locales)){
                // If found, return the language
                return $value;
            }
          }
      }
      // If we can't find a supported language, we use the default
      return $this->getDefaultLocale();
    }

    // Warning: parameter positions are changed!
    function _loadTranslationTable($locale, $path='') {
      if (empty($locale))
        $locale = $this->getDefaultLocale();
      $language = $this->_languageTable[$locale];
      if (empty($language)) {
        die ("LANGUAGE Error: Unsupported locale '$locale'");
      }
      if (!is_array($this->_translationTable[$language])) {
        die ("LANGUAGE Error: Language '$language' not available");
      }
      if(empty($path))
        $path = 'languages/'.$this->_languageTable[$locale].'/global.lng';
      if (isset($this->_loadedTranslationTables[$language])) {
        if (in_array($path, $this->_loadedTranslationTables[$language])) {
          // Translation table was already loaded
          return true;
        }
      }
      if (file_exists($path)) {
        $entries = file($path);
        $this->_translationTable[$language][$path] = Array();
        $this->_loadedTranslationTables[$language][] = $path;
        foreach ($entries as $row) {
          if (substr(ltrim($row),0,2) == '//') // ignore comments
            continue;
          $keyValuePair = explode('=',$row);
          // multiline values: the first line with an equal sign '=' will start a new key=value pair
          if(sizeof($keyValuePair) == 1) {
            $this->_translationTable[$language][$path][$key] .= ' ' . chop($keyValuePair[0]);
            continue;
          }
          $key = trim($keyValuePair[0]);
          $value = $keyValuePair[1];
          if (!empty($key)) {
            $this->_translationTable[$language][$path][$key] = chop($value);
          }
        }
        return true;
      }
      return false;
    }

    // Warning: parameter positions are changed!
    function _unloadTranslationTable($locale, $path) {
      $language = $this->_languageTable[$locale];
      if (empty($language)) {
        die ("LANGUAGE Error: Unsupported locale '$locale'");
      }
      unset($this->_translationTable[$language][$path]);
      foreach($this->_loadedTranslationTables[$language] as $key => $value) {
        if ($value == $path) {
          unset($this->_loadedTranslationTables[$language][$key]);
          break;
        }
      }
      return true;
    }

    function loadCurrentTranslationTable() {
      $this->_loadTranslationTable($this->getCurrentLocale());
    }

    // Warning: parameter positions are changed!
    function loadTranslationTable($locale, $path) {
      // This method is only a placeholder and wants to be overwritten by YOU! ;-)
      // Here's a example how it could look:
      if (empty($locale)) {
        // Load default locale of no one has been specified
        $locale = $this->getDefaultLocale();
      }
      // Select corresponding language
      $language = $this->_languageTable[$locale];
      // Set path and filename of the language file
      $path = "languages/$language/$path.lng";
      // _loadTranslationTable() does the rest
      $this->_loadTranslationTable($locale, $path);
    }

    // Warning: parameter positions are changed!
    function unloadTranslationTable($locale, $path) {
      // This method is only a placeholder and wants to be overwritten by YOU! ;-)
      $this->_unloadTranslationTable($locale, $path);
    }

    function getTranslation($key) {
      $trans = $this->_translationTable[$this->_currentLanguage];
      if (is_array($trans)) {
        foreach ($this->_loadedTranslationTables[$this->_currentLanguage] as $table) {
          if (isset($trans[$table][$key])) {
            return $trans[$table][$key];
          }
        }
      }
      return $key;
    }

  }
?>

I have copied these two classes from a bigger project which uses some more methods

and has alot more functionality. So I'm not sure if the above will work for you,

I haven't tested it yet. Also it might seem some functions are a bit complicated

or have unused parameters. Feel free to modify it ;-)

Addition:

Change these line if translation of dynamic data is needed ;-)

$this->register_prefilter("smarty_prefilter_i18n");

to this

$this->register_outputfilter("smarty_prefilter_i18n");

//this change makes it possible even to translate dynamic data e.g. options because translation is done after compilation of template [xaos, 20050206]

Example

Here are two simple language files

// English language file
// put in languages/eng/global.lng
NG_OK=Ok
NG_ABORT=Abort
NG_CANCEL=Cancel
NG_MULTILINE=This entry spans 2 lines, this is the first
and this is the second.
NG_HELLO_WORLD=Hello World!<br>How do you do?
// German language file
// put in languages/deu/global.lng
NG_OK=Ok
NG_ABORT=Abbrechen
NG_CANCEL=Abbrechen
NG_MULTILINE=Dieser Eintrag umfasst 2 Zeile, diese ist die erste
und diese ist die zweite.
NG_HELLO_WORLD=Hallo Welt!<br>Wie geht es Dir?

Put these files into the specified paths.

And finally a template with multilanguage support:

<html>
  <body>
    ##NG_HELLO_WORLD##<br>
    ##NG_MULTILINE##<br>
    <input type="button" value="##NG_OK##" onclick="alert('##NG_OK##')">
    <input type="button" value="##NG_CANCEL##" onclick="alert('##NG_CANCEL##')">
  </body>
</html>

Finally how to use the smartyML class:

<?php
  $smarty = new smartyML();
  // [...]
  // $smarty->assign(...);
  $smarty->display("myTemplate.tpl");
?>

Easy, isn't it? The language will be determined by the browser settings. If you want

to change it explicitly you can use something like this:

<?php
  $smarty = new smartyML("de"); // now the language will be german regardless of the browser settings
  // [...]
  // $smarty->assign(...);
  $smarty->display("myTemplate.tpl");
?>

Here's a list of some locales returned from browser:

Afrikaans = af

Albanian = sq

Arabic (Algeria) = ar-dz

Arabic (Bahrain) = ar-bh

Arabic (Egypt) = ar-eg

Arabic (Iraq) = ar-iq

Arabic (Jordan) = ar-jo

Arabic (Kuwait) = ar-kw

Arabic (Lebanon) = ar-lb

Arabic (libya) = ar-ly

Arabic (Morocco) = ar-ma

Arabic (Oman) = ar-om

Arabic (Qatar) = ar-qa

Arabic (Saudi Arabia) = ar-sa

Arabic (Syria) = ar-sy

Arabic (Tunisia) = ar-tn

Arabic (U.A.E.) = ar-ae

Arabic (Yemen) = ar-ye

Arabic = ar

Armenian = hy

Assamese = as

Azeri (Cyrillic) = az

Azeri (Latin) = az

Basque = eu

Belarusian = be

Bengali = bn

Bulgarian = bg

Catalan = ca

Chinese (China) = zh-cn

Chinese (Hong Kong SAR) = zh-hk

Chinese (Macau SAR) = zh-mo

Chinese (Singapore) = zh-sg

Chinese (Taiwan) = zh-tw

Chinese = zh

Croatian = hr

Czech = cs

Danish = da

Divehi = div

Dutch (Belgium) = nl-be

Dutch (Netherlands) = nl

English (Australia) = en-au

English (Belize) = en-bz

English (Canada) = en-ca

English (Caribbean) = en

English (Ireland) = en-ie

English (Jamaica) = en-jm

English (New Zealand) = en-nz

English (Philippines) = en-ph

English (South Africa) = en-za

English (Trinidad) = en-tt

English (United Kingdom) = en-gb

English (United States) = en-us

English (Zimbabwe) = en-zw

English = en

Estonian = et

Faeroese = fo

Farsi = fa

Finnish = fi

French (Belgium) = fr-be

French (Canada) = fr-ca

French (France) = fr

French (Luxembourg) = fr-lu

French (Monaco) = fr-mc

French (Switzerland) = fr-ch

FYRO Macedonian = mk

Gaelic = gd

Georgian = ka

German (Austria) = de-at

German (Germany) = de

German (Liechtenstein) = de-li

German (lexumbourg) = de-lu

German (Switzerland) = de-ch

Greek = el

Gujarati = gu

Hebrew = he

Hindi = hi

Hungarian = hu

Icelandic = is

Indonesian = id

Italian (Italy) = it

Italian (Switzerland) = it-ch

Japanese = ja

Kannada = kn

Kazakh = kk

Konkani = kok

Korean = ko

Kyrgyz = kz

Latvian = lv

Lithuanian = lt

Malay (Brunei) = ms

Malay (Malaysia) = ms

Malayalam = ml

Maltese = mt

Marathi = mr

Mongolian (Cyrillic) = mn

Nepali (India) = ne

Norwegian (Bokmal) = nb-no

Norwegian (Bokmal) = no

Norwegian (Nynorsk) = nn-no

Oriya = or

Polish = pl

Portuguese (Brazil) = pt-BR

Portuguese (Portugal) = pt

Punjabi = pa

Rhaeto-Romanic = rm

Romanian (Moldova) = ro-md

Romanian = ro

Russian (Moldova) = ru-md

Russian = ru

Sanskrit = sa

Serbian (Cyrillic) = sr

Serbian (Latin) = sr

Slovak = sk

Slovenian = ls

Sorbian = sb

Spanish (Argentina) = es-ar

Spanish (Bolivia) = es-bo

Spanish (Chile) = es-cl

Spanish (Colombia) = es-co

Spanish (Costa Rica) = es-cr

Spanish (Dominican Republic) = es-do

Spanish (Ecuador) = es-ec

Spanish (El Salvador) = es-sv

Spanish (Guatemala) = es-gt

Spanish (Honduras) = es-hn

Spanish (International Sort) = es

Spanish (Mexico) = es-mx

Spanish (Nicaragua) = es-ni

Spanish (Panama) = es-pa

Spanish (Paraguay) = es-py

Spanish (Peru) = es-pe

Spanish (Puerto Rico) = es-pr

Spanish (Spain) = es-es

Spanish (Traditional Sort) = es

Spanish (United States) = es-us

Spanish (Uruguay) = es-uy

Spanish (Venezuela) = es-ve

Sutu = sx

Swahili = sw

Swedish (Finland) = sv-fi

Swedish = sv

Syriac = syr

Tamil = ta

Tatar = tt

Telugu = te

Thai = th

Tsonga = ts

Tswana = tn

Turkish = tr

Ukrainian = uk

Urdu = ur

Uzbek (Cyrillic) = uz

Uzbek (Latin) = uz

Vietnamese = vi

Xhosa = xh

Yiddish = yi

Zulu = zu