PHP

PHP & OOP - Mehrsprachige Webanwendungen (PHP Tutorial)

Tutorial erstellt von Andreas W. in 5.x, letzte Änderung am 22.06.2010

In dem nachfolgenden Tutorial möchte ich mit euch die Grundlagen der Implementierung von Mehrsprachigkeit in einer Webanwendung unter Verwendung von PHP besprechen. Das Ziel dieses Tutorials ist eine Klasse zu entwerfen, die das Laden von Sprachdateien und das Abfragen von Sprachtags ermöglicht. Am Ende möchte ich euch zeigen, wie ihr diese einsetzen könnt um eure Projekte und Bibliotheken anwenderfreundlicher zu gestalten. Zu diesem Zweck werde ich nicht nur einfach die einzelne Klassen vorstellen, sondern auch einige Beispiele aus der Praxis anführen. Die gezeigten Anwendungen und Methoden werde ich dabei ausführlich erläutern.

Mögliche Ansätze

Die unendlichen Weiten des Internets sorgen heute dafür, dass viele Webapplikationen nicht mehr ohne Mehrsprachigkeit auskommen. Seiten wie Amazon, Ebay aber auch Facebook oder Google würden tausende Kunden bzw. Besucher verlieren, würden sie ihre Seiten nur auf Deutsch anbieten. Selbst der Einsatz von Weltsprachen wie Englisch kann dieses Problem grundsätzlich nicht lösen.

Hat man sich nun dazu entschlossen, seine Internetpräsenz in mehreren Sprachen anzubieten, stellt sich die Frage nach einer möglichen Umsetzung. Der Einsatz von PHP ermöglicht es nun unter Verwendung unterschiedlichster Technologien an ein solches Problem heranzugehen. Die gängigsten Varianten sind wohl der Einsatz des PHP eigenen gettext() Moduls, einer Datenbank, einer *.xml-, aber auch *.ini-basierten Lösung. Man muss sich also als erstes zwischen einem bereits existierenden Modul, wie gettext, und einer eigenen Implementierung, wie im Falle einer Datenbank, entscheiden.

Wichtige Vorüberlegungen

Das PHP eigene gettext() Modul ist leider relativ kompliziert und lässt sich meiner Meinung nach nur recht schlecht in eigene Implementationen integrieren. Daher werden wir in diesem Fall eine eigene Language Klasse implementieren.

Bleibt also noch die Frage nach dem verwendeten Speichermedium. Da eine Datenbank jedoch erst einmal vorhanden sein muss und unsere Klasse auch bei praktisch statischen Seiten eingesetzt werden soll, bleibt nur noch die Wahl zwischen dem Einsatz von *.xml und *.ini Dateien.

Der Einsatz von *.xml Dateien ist zwar Dank des seit PHP 5 verfügbaren SimpleXML Moduls relativ komfortabel, jedoch müsste man sich immer noch um eine geeignete Speicherung innerhalb der Klasse kümmern, was ein Nachteil gegenüber *.ini Dateien darstellt. Diese können ebenfalls mit Hilfe einer einfachen Funktion, parse_ini_file(), geparst werden, werden dann aber nicht als Objekt zurückgegeben, das noch verarbeitet werden muss, sondern als praktisches Array. Wir werden deshalb in unserer Language Klasse *.ini Dateien verarbeiten.

Benötigte Funktionalitäten

Da wir uns nun auf eine Technologie festgelegt haben, sollten wir uns Gedanken über die benötigten Funktionalitäten unserer Klasse machen.

Selbstverständlich soll es möglich sein, eine Standard-Sprache für eine Applikation festzulegen. Da wir unserem Benutzer jedoch anbieten wollen auch eine andere Sprache, zum Beispiel über ein DropDown-Menü auszuwählen, benötigen wir auch eine Möglichkeit die aktuell verwendete Sprache festzulegen. Es ist mit diesen Daten und den auf ihnen operierenden Funktionen zwar möglich die Standard Sprache sowie des Benutzers zu setzen und zu ermitteln, jedoch können wir noch keine Daten laden. Es wird also eine Methode benötigt, welche die *.ini Dateien lädt. Da nun eigentlich alle wichtigen Informationen/Daten vorhanden sind, müssen wir abschließend noch eine Möglichkeit bieten, abhängig von der momentan eingestellten Sprache des Benutzers so genannte Language-Tags, die in den einzelnen Sprachdateien hinterlegt sind, abzufragen.

Die Implementation

Wir können nun also mit der eigentlichen Implementierung unserer Language-Klasse beginnen. Diese sollten wir als Singleton implementieren, da es ja wenig Sinn macht, viele verschiedene Spracheinstellungen für einen Benutzer zu verwenden. Sowohl der Konstruktor, als auch die __clone() Methode müssen folglich als private deklariert werden. Außerdem müssen wir ein statisches Klassen-Attribut einführen, das eine Instanz der Klasse hält sowie eine Methode definieren, die auf dieses Attribut zugreifen kann und eine Instanz der Klasse liefert. Basierend auf unseren schon zuvor getätigten Vorüberlegungen, müssen wir also die folgenden Methoden definieren.

* getInstance()
* __construct()
* __clone()
* setDefault($lang)
* setLanguage($lang)
* addLanguageTags($config)
* get($langTag)
* offsetExists($offset)
* offsetGet($offset)
* offsetSet($offset, $value)
* offsetUnset($offset)

Der aufmerksame Leser wird bemerkt haben, dass wir die letzten vier Methoden zuvor nicht besprochen haben. Sie dienen rein der Möglichkeit, die Language Klasse wie ein Array anzusprechen. Sie werden vom ArrayAccess Interface gefordert, das uns diese Möglichkeit eröffnet. Es ist uns dann nicht nur möglich via

Code:
print $langObj->get('LanguageTag');


auf ein Language-Tag zuzugreifen, sondern auch mit

Code:
print $langObj['LanguageTag'];
.

Das finde ich persönlich sehr angenehm.

Die Klasse selbst ist eigentlich selbsterklärend. Ich möchte dennoch kurz auf die Methode addLanguageTags() eingehen, die das Laden bzw. Hinzuladen von Sprachdateien ermöglicht. Es ist bei ihrer Implementierung sehr wichtig darauf zu achten, dass die bereits geladenen Language-Tags nicht überschrieben werden, außer sie kommen tatsächlich doppelt vor. Es muss also die Sprachdatei mit Hilfe der Funktion parse_ini_file() in ein temporäres Array geladen werden, das dann unter Verwendung der Funktion array_merge_recursivr() mit den bisher geladenen Daten zusammengeführt wird. Da es natürlich auch passieren kann, dass eine Datei geladen werden soll, die überhaupt nicht existiert, müssen wir diesen Fall auch Abfragen und eine Fehlerbehandlung anstoßen. Wir implementieren zu diesem Zweck eine LanguageException.

Code:

<?php

/**
* @package core::language
* @class LanguageException
*
* The LanguageException class implements a
* Exception type for the Language class.
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 25.05.2010<br />
*/
class LanguageException extends Exception {}

?>


Da wir nun auch die Implementierung der addLanguageTags() Methode abgeschlossen, den Grund für die Einbindung des ArrayAccess Interfaces geklärt und die Implementierung der LanguageException Klasse abgeschlossen haben, ist der Rest nun nicht mehr allzu schwierig. Daher findet ihr nachfolgend die Language Klasse.

Code:

<?php

/**
* @package core::language
* @class Language
*
* The Language class enables the programmer
* to develop multi language websites. The
* translation parameters are stored within an
* *.ini file grouped by the languages supported
* by the current application. The current language
* is predefined but could also be set by the user
* using a drop-down or something else.
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 14.02.2010
* Version 0.2, 25.05.2010 (Updated to singleton)<br />
* Version 0.3, 25.05.2010 (ArrayAccess interface implemented)<br />
*/
class Language implements ArrayAccess {
  
   /**
    * @private
    * The current class instance.
    */
   private static $instance = null;

   /**
    * @private
    * Holds the indicator of the current language.
    */
   private $lang = null;

   /**
    * @private
    * Holds the indicator of the default language.
    */
   private $defaultLang = null;

   /**
    * @private
    * Holds all language tags.
    */
   private $langTags = null;

   /**
    * @public
    *
    * Returns an instance of the Language class.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010<br />
    */
   static public function getInstance() {
      // Check if an instance already exists ...
      if( self::$instance === null ) {
         // ... and create one if not.
         self::$instance = new self;
      }
      
      // Return the instance.
      return self::$instance;
   }

   /**
    * @private
    *
    * The default constructor assigns the default
    * and the current language.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 14.02.2010
    * Version 0.2, 21.05.2010 (Updated to singleton)<br />
    */
   private function __construct() {

      // Assign the default ...
      if( $this->defaultLang === null) {
         $this->defaultLang = 'en';
      }

      // ... and the current language.
      if( $this->lang === null ) {
         $this->lang = $this->defaultLang;
      }
      
      // And initialize the language stack.
      if( $this->langTags === null ) {
         $this->langTags = array();
      }
   }
  
   /**
    * @private
    *
    * Avoid the cloning of this class.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010 <br />
    */
   private function __clone() {}
  
   /**
    * @public
    *
    * Sets the default language.
    *
    * @param string $lang The language identifier.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 14.02.2010
    */
   public function setDefault($lang) {
      if( $this->lang == $this->defaultLang ) {
         $this->lang = $lang;
      }

      $this->defaultLang = $lang;
   }
  
   /**
    * @public
    *
    * Sets the current language.
    *
    * @param string $lang The language identifier.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 14.02.2010
    */
   public function setLanguage($lang) {
      $this->lang = $lang;
   }

   /**
    * @public
    *
    * Loads all language tags from a given configuration
    * file and adds them to an array.
    *
    * @param string $config The language file.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 14.02.2010
    */
   public function addLanguageTags($config) {
      // Check if the given language file exists, ...
      if( file_exists($config) ) {
         // ... load the language configuration ...
         $tempLangTags = parse_ini_file($config, true);

         // ... and add them to the public language tag array.
         $this->langTags = array_merge_recursive($this->langTags, $tempLangTags);
      }
      
      // If the file cannot be loaded, ...
      else {
         // ... throw an exception.
         throw new LanguageException('Failed to open ' . $config);
      }
   }

   /**
    * @public
    *
    * Returns the text for the given language tag
    * in the current language if available. Otherwise
    * it returns the default value.
    *
    * @param string $langTag The language tag.
    * @return The text in the current language.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 14.02.2010
    */
   public function get($langTag) {
      
      // Check if the tag for the current language exists ...
      if( isset($this->langTags[$this->lang][$langTag]) ) {
         return $this->langTags[$this->lang][$langTag];
      }

      else if( isset($this->langTags[$this->defaultLang][$langTag]) ) {
         return $this->langTags[$this->defaultLang][$langTag];
      }

      return null;
   }
  
   /**
    * @public
    *
    * Validates if the language tag exists within the current
    * language configuration.
    *
    * @param string $offset The language tag.
    * @return True or false.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010
    */
   public function offsetExists($offset) {
      return isset($this->langTags[$this->lang][$offset]) ? true : false;
   }
  
   /**
    * @public
    *
    * Returns the text for the given language tag
    * in the current language if available. Otherwise
    * it returns the default value.
    *
    * @param string $offset The language tag.
    * @return The text in the current language.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010
    */
   public function offsetGet($offset) {
      return $this->get($offset);
   }
  
   /**
    * @public
    *
    * Just a placeholder required by the
    * ArrayAccess interface.
    *
    * @param string $offset The language tag.
    * @param string $value The new value of the language tag.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010
    */
   public function offsetSet($offset, $value) {
      // Do nothing!
   }
  
   /**
    * @public
    *
    * Just a placeholder required by the
    * ArrayAccess interface.
    *
    * @param string $offset The language tag.
    *
    * @author Andreas Wilhelm
    * @version
    * Version 0.1, 25.05.2010
    */
   public function offsetUnset($offset) {
      // Do nothing!
   }
}

?>


Beispiele aus der Praxis

Nun, da wir die Implementation unserer Language Klasse abgeschlossen haben, wollen wir uns zwei Beispiele aus der Praxis ansehen. Da die gängigsten und auch einfachsten Beispiele für eine Anwendung von Mehrsprachigkeit Eingabemasken sind, werden wir uns daher die Umsetzung eines einfachen Kontakt- und Feedback-Formulars ansehen. Diese werden die folgenden beiden Sprachdateien verwenden.

contact.ini
Code:

[en]
Contact.Name = "Name"
Contact.Mail = "Mail Address"
Contact.Message = "Message"
Contact.Send = "Send"
Contact.Reset = "Reset"

[de]
Contact.Name = "Name"
Contact.Mail = "EMail Adresse"
Contact.Message = "Nachricht"
Contact.Send = "Absenden"
Contact.Reset = "Zurücksetzen"


feedback.ini
Code:

[en]
Feedback.Name = "Lastname, Firstname"
Feedback.Mail = "Mail Address"
Feedback.Message = "Message"
Feedback.Send = "Send Feedback"

[de]
Feedback.Name = "Nachname, Vorname"
Feedback.Mail = "EMail Adresse"
Feedback.Message = "Rückmeldung"
Feedback.Send = "Rückmeldung Absenden"


Das Kontakt-Formular soll sowohl auf Deutsch als auch auf Englisch verfügbar sein. Wir wollen dabei die Standard-Sprache Deutsch verwenden. Wir holen uns folglich eine Instanz der Language-Klasse, setzen die neue Standard-Sprache und erzeugen unser Formular, das wir anschließend ausgeben.

Code:

<?php

// Load the language class.
require_once('Language.php');

// ... and initialize the test environment.
try {
   // Now get an instance of the language class.
   $lang = Language::getInstance();

   // Then load the language files ...
   $lang->addLanguageTags('contact.ini');

   // ... and set the default language.
   $lang->setDefault('de');
  
   // Create the contact form ...
   $contact = "<form action=\"\" method=\"post\">";
   $contact .= "<table>";
   $contact .= "<tr><th>" . $lang->get('Contact.Name') . "</th><td><input type=\"text\" name=\"name\" \></td></tr>";
   $contact .= "<tr><th>" . $lang->get('Contact.Mail') . "</th><td><input type=\"text\" name=\"mail\" \></td></tr>";
   $contact .= "<tr><th>" . $lang->get('Contact.Message') . "</th><td><textarea name=\"message\" ></textarea></td></tr>";
   $contact .= "<tr><td></td><td><input type=\"submit\" name=\"submit\" value=\"" . $lang->get('Contact.Send') . "\" \><input type=\"reset\" name=\"reset\" value=\"" . $lang->get('Contact.Reset') . "\" \></td></tr>";
   $contact .= "</table>";
   $contact .= "</form>";

   // ... and dump it.
   echo $contact;
}

catch(LanguageException $e) {
   echo $e->getMessage();
}
?>


Das war doch relativ einfach. Nun wollen wir nach dem Kontakt-Formular und anderen Inhalten noch ein Feedback-Formular ausgeben, das jedoch nun auf Englisch angezeigt werden soll. Es ist also nicht nur nötig die Sprache des Benutzers auf Englisch umzustellen, sondern auch mehrere Sprachdateien zu laden. Das ist aber kein Problem.

Code:

<?php

// Load the language class.
require_once('Language.php');

// ... and initialize the test environment.
try {
   // Now get an instance of the language class.
   $lang = Language::getInstance();

   // Then load the language files ...
   $lang->addLanguageTags('contact.ini');
   $lang->addLanguageTags('feedback.ini');

   // ... and set the default language.
   $lang->setDefault('de');

   // Output the contact form and other content.
  
   // Finally set a new current language ...
   $lang->setLanguage('en');
  
   // Create the feedback form ...
   $feedback = "<form action=\"\" method=\"post\">";
   $feedback .= "<table>";
   $feedback .= "<tr><th>" . $lang->get('Feedback.Name') . "</th><td><input type=\"text\" name=\"name\" \></td></tr>";
   $feedback .= "<tr><th>" . $lang->get('Feedback.Mail') . "</th><td><input type=\"text\" name=\"mail\" \></td></tr>";
   $feedback .= "<tr><th>" . $lang->get('Feedback.Message') . "</th><td><textarea name=\"message\" ></textarea></td></tr>";
   $feedback .= "<tr><td></td><td><input type=\"submit\" name=\"submit\" value=\"" . $lang->get('Feedback.Send') . "\" \></td></tr>";
   $feedback .= "</table>";
   $feedback .= "</form>";
  
   // ... and dump it.
   echo $feedback;
}

catch(LanguageException $e) {
   echo $e->getMessage();
}
?>


Ich hoffe diese Beispiele konnten euch einen kleinen Überblick über die möglichen Einsatzgebiete und die Art der Verwendung der Language Klasse bieten.

Fragen und Antworten

Fragen und Antworten findet ihr selbstverständlich bei uns im Forum. Ich möchte euch auch darum bitten, keine Fragen an mich persönlich zu senden aus dem einfachen Grund, dass andere Menschen dann nichts von den gegebenen Antworten haben. Zudem können euch im Forum auch viele andere Leute, die echt Ahnung haben, helfen. Natürlich freue ich mich über ein kurzes Feedback zu diesem Tutorial. Ich bedanke mich für eure Aufmerksamkeit und hoffe, dass euch mein kurzer Exkurs gefallen hat.

Liebe Grüße,

Andreas

http://www.avedo.net

>> Allgemeine Fragen oder Probleme mit dem Tutorial? Hier gehts zum Forum!

Impressum / Datenschutzerklärung          © der-Webdesigner.net 2002 - 2010           top ▲