<?php
/********************************************************************
*
* Project: UNIVERSAL LANGUAGE TOOL
* Version: 0.4.1
*
* The goal of this library is to provide complete, feature rich
* but easy to use API for multilanguage support.
*
* Features:
*
* - single file library
* - it does not depend on any other library
* - allows use of unlimited number of languages
* - allows using different scripts for the same language
* (conversion from cyrillic to latin characters, for instance)
* - to add new language, you do not have to write any code, but just to
* create language definition, dictionaires and conversion tables
* - library does not just replace predefined macros with language
* expressions but also does character conversions if necessary
*
* This is free software. You may use it and redistribute it. You
* may change code to suit your needs but you may not distribute
* changed code.
*
* Author: Predrag Supurovic
*
* (c)2004 Copyright by DataVoyage, http://www.datavoyage.com/
*
/********************************************************************/
define ('LANG_DEF_FILE', 'ldef_');
define ('LANG_DICT_FILE', 'ldct');
define ('ERROR_NO_LNG_DEF_FILE', 'Cannot find language definition file');
define ('ERROR_NO_LNG_DICT_FILE', 'Cannot find language dictionary file');
class ULT {
var $source_language = '';
// language used in design mode for text, and naming documents
// When you create site you create it as it is single language butuse this language.
// Also, when you need to load external documents use this language in naming them (document has to be ended with =<langid>=
var $display_language = '';
// language that should be displayed for current user. Library will know how to handle
// contents prepared in source language and display it in current language
var $lang_defs;
// Global var $lang_defs contains info about supported language definitions and dictionaires.
// Definitions are stored as multidimensional array. The top array is indexed by language id,
// and each record contains arrays with definitions for specific language. Language definition
// is also multidimensional array
// Structure:
// $lang_defs;
// [language id] => Array
// [id] => language id
// [name] => language name, used for description
// [codepage] => codepage that should be seto for HTML document to present text using this language
// [is_source_lng] => 1, if this language is source language for site
// [parent] => languge id of parent language
// [dictionary] => Array
// [dictionary item] => value
// [dictionary item] => value
// [dictionary item] => value
// [dictionary itm] => value
// [parent_conversion_table] => Array
// [source string] => replacement string
// [source string] => replacement string
// [source string] => replacement string
// [source string] => replacement string
//
// This allows using multiple languages on the same page. It is not recomended to store complete dictionaires
// for multiple languages to preserve memory resources. Store only limited number of items which are needed to be
// presented in several lanbguages on the same page like language names
// function ULT($p_lang_dir = 'lang/')
// Constructor function. Call this to create object. Optional parameter
// is path to directory where language definition files are stored.
function ULT($p_lang_dir = 'lang/') {
$this->lang_dir = $p_lang_dir;
$this->load_language_defs();
}
//
// function process($tpl_source)
//
// Do all language processing:
// - replaces all language macros with items according to dictionary
// - replaces all family id's in document (targeted to urls to external documents and links)
// - replaces all language id's in document (targeted to urls to external documents and links)
// - tanscribes characters and phrases from one language to another (or one language script to another)
function process($tpl_source) {
$m_slng = $this->source_language;
$m_dlng = $this->display_language;
$m_slngf = $this->get_family ($this->source_language);
$m_dlngf = $this->get_family ($this->display_language);
// Replace the matched language strings with the entry in the file
$m_result = preg_replace_callback('/##(.+?)##/', array(&$this, '_compile_lang'), $tpl_source);
$m_file_lng_pattern = "/=%25$m_slngf=/";
$m_file_lng_replace = "=%25$m_dlngf=";
$m_result = preg_replace($m_file_lng_pattern, $m_file_lng_replace, $m_result);
$m_file_lng_pattern = "/=$m_slng=/";
$m_file_lng_replace = "=$m_dlng=";
$m_result = preg_replace($m_file_lng_pattern, $m_file_lng_replace, $m_result);
$m_result = $this->transcribe_document($m_result);
return $m_result;
}
//
// _compile_lang
// Called by language_process to replace each language macro with actual text
//
function _compile_lang($p_key) {
//global $lang_defs;
if ($this->lang_defs[$this->display_language]['dictionary']
and ($this->lang_defs[$this->display_language]['dictionary'][$p_key[1]]))
{
$m_result = $this->lang_defs[$this->display_language]['dictionary'][$p_key[1]];
} else {
$m_result = $p_key[0];
}
return $m_result;
}
//
// set_display_language ($p_lang)
// Set display language to $p_lang. If invalid language, set to source language.
//
function set_display_language ($p_lang) {
//global $lang_defs;
if ($this->language_defined ($p_lang)) {
$this->display_language = $p_lang;
} else {
$this->display_language = $this->source_language;
}
}
//
// set_default_language ($p_lang)
// Set default language to $p_lang. If invalid language, set first language in array.
//
function set_source_language ($p_lang) {
//global $lang_defs;
if ($this->language_defined ($p_lang)) {
$this->source_language = $p_lang;
} else {
$m_keys = array_keys($this->lang_defs);
$this->source_language = $m_keys[0];
}
}
//
// set_language ($p_lang, $p_lang_dir)
// Set language definition (add sub_array to $lang_defs)
//
function set_language_def ($p_lang, $p_lang_dir = '') {
//global $lang_defs;
if (! $p_lang_dir) $p_lang_dir = $this->lang_dir;
$m_langdef = $p_lang_dir . '/' . LANG_DEF_FILE . $p_lang . '.php';
if (file_exists($m_langdef)) {
include ($m_langdef);
$this->lang_defs[$p_lang] = $lng_def;
if ($this->lang_defs[$p_lang]['is_source_lng'] == 1) {
$this->set_source_language ($p_lang);
$this->set_display_language ($p_lang);
}
} else {
echo ERROR_NO_LNG_DEF_FILE . ' (' . $m_langdef . ')';
}
}
//
// load_language_defs($p_lang_dir = '')
// Load definitions from all language definition files in target directory.
// If directory is not specified, current directory is used.
//
function load_language_defs($p_lang_dir = '') {
//global $lang_defs;
if (! $p_lang_dir) $p_lang_dir = $this->lang_dir;
if ($handle = opendir($p_lang_dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match ("/^" . LANG_DEF_FILE ."(.*).php/i", $file)) {
$m_lang = substr($file, strlen(LANG_DEF_FILE), strlen($file)-strlen(LANG_DEF_FILE)-4);
$this->set_language_def ($m_lang, $p_lang_dir);
$m_lang_dict = $p_lang_dir . '/' . LANG_DICT_FILE . '_';
$this->set_dictionary ($m_lang_dict, $m_lang);
}
}
asort ($this->lang_defs);
if (! $this->source_language) {
$this->set_source_language ('');
$this->set_display_language ($this->source_language);
}
} else {
echo ERROR_LOADING_LANG_DEFS . ' (Directory: ' . $p_lang_dir . ')';
}
}
//
// function language_defined ($p_lang)
// Returns true if language definition is loaded
//
function language_defined ($p_lang) {
//global $lang_defs;
if ($p_lang) {
return isset ($this->lang_defs[$p_lang]);
} else {
return false;
}
}
//
// function display_language_has_parent()
// returnbs true if display language has parent
//
function display_language_has_parent() {
//global $lang_defs;
$m_disp_lang = $this->lang_defs[$this-$display_language];
return $m_disp_lang['parent'] and $this->language_defined ($m_disp_lang['parent']);
}
//
// transcribe ($p_text, $p_trans_table)
// Convert characters from one script to another using transcription table
//
function transcribe ($p_text, $p_trans_table) {
if ($p_trans_table) {
return strtr ($p_text, $p_trans_table);
} else {
return $p_text;
}
}
//
// transcribe_dictionary ($p_lang, $p_dictionary)
// Convert dictionary array from parent language to current. Used to transcribe from
// different codepage or script. If dictionary for the language may be created by
// transcribing parent's dictionary then do this a nd do not define whole ditionary again.
// That will make dictionary maintenance easier.
//
function transcribe_dictionary ($p_lang, $p_dictionary) {
//global $lang_defs;
if ($p_dictionary) {
foreach ($p_dictionary as $key => $value) {
$p_dictionary[$key] = $this->transcribe($value, $this->lang_defs[$p_lang]['parent_conversion_table']);
}
}
return $p_dictionary;
}
//
// transcribe_parent_language_dictionary ($p_lang)
// Convert dictionary of loaded parent language to current. Used to transcribe from different codepage or script.
//
function transcribe_parent_language_dictionary ($p_lang) {
//global $lang_defs;
$this->lang_defs[$p_lang]['dictionary'] = $this->transcribe_dictionary ($p_lang, $this->lang_defs[$p_lang]['dictionary']);
// foreach ($lang_defs[$p_lang]['dictionary'] as $key => $value) {
// $this->lang_defs[$p_lang]['dictionary'][$key] = $this->transcribe($value, $this->lang_defs[$p_lang]['parent_conversion_table']);
// }
}
//
// transcribe_document ($p_lang)
// If display language has parent language and source document is written in parent language then we should
// transcribe document contents from parent language to display language
//
function transcribe_document($p_doc) {
//global $lang_defs;
$m_disp_lang = $this->lang_defs[$this->display_language];
$m_src_lang = $this->lang_defs[$this->source_language];
if ($m_disp_lang['parent']
and ($m_disp_lang['id'] != $m_src_lang['id'])
and ($m_disp_lang['parent'] == $m_src_lang['id']))
{
return $this->transcribe($p_doc, $m_disp_lang['parent_conversion_table']);
} else {
return $p_doc;
}
}
//
// function filename_from_template ($p_dict_filename, $p_lang)
// Creates full file name based on template and language
// If language has parent then filename will be created due to parent language
// Always use this to load files that corresponds to needed language.
//
function filename_from_template ($p_dict_filename, $p_lang, $do_parent = true) {
//global $lang_defs;
$m_fname = $p_dict_filename;
if ($do_parent and $this->lang_defs[$p_lang]['parent']) {
$m_fname .= $this->lang_defs[$p_lang]['parent'];
} else {
$m_fname .= $p_lang;
}
$m_fname .= '.php';
return $m_fname;
}
//
// load_dictionary ($p_dict_filename, $p_lang, $p_add = false)
// Load dictionary table from specified file into specified language definition.
// If $p_add is true then definition is appended to alerady existing definitions.
//
function load_dictionary ($p_dict_filename, $p_lang, $p_add = false) {
//global $lang_defs;
if (file_exists($p_dict_filename)) {
include ($p_dict_filename);
$this->lang_defs[$p_lang]['parent_conversion_table'] = array_merge ($this->lang_defs[$p_lang]['parent_conversion_table'], $lngt);
if ($this->lang_defs[$p_lang]['parent']) {
$lng = $this->transcribe_dictionary ($p_lang, $lng);
}
if ($p_add == 0) {
$this->lang_defs[$p_lang]['dictionary'] = $lng;
} else {
$this->lang_defs[$p_lang]['dictionary'] = array_merge ($this->lang_defs[$p_lang]['dictionary'], $lng);
}
}
}
//
// set_dictionary ($p_dict_filename, $p_lang, $p_add = false)
// Load dictionary table from specified file into specified language definition.
// If $p_add is true then definition is appended to alerady existing definitions.
// If language has parent, then parent's file will be loaded first and after that
// child's file. This helps dictionary maintenance. Most of the items may be stored in
// parent's dictionary and only differences in childs dictionary (example, for 'en' as parent and
// 'en-us' as a child, most of the items are the same but some are not, like
// 'color'/'colour' pair. This allows you to have item 'colour' in parent dictionary of 'en' among
// all others and just item 'color' for 'child 'en-us'.
//
//
function set_dictionary ($p_dict_filename, $p_lang, $p_add = false) {
//global $lang_defs;
$m_dictdef = $this->filename_from_template ($p_dict_filename, $p_lang);
$this->load_dictionary ($m_dictdef, $p_lang, $p_add);
if ($this->lang_defs[$p_lang]['parent']) {
$m_dictdef = $this->filename_from_template ($p_dict_filename, $p_lang, false);
$this->load_dictionary ($m_dictdef, $p_lang, true);
}
}
//
// add_dictionary ($p_dict_filename, $p_lang)
// Load dictionary table from specified file into specified language definition.
// It appends new definition to existing one.
//
function add_dictionary ($p_dict_filename, $p_lang) {
$this->set_dictionary ($p_dict_filename, $p_lang, true);
}
//
// sync_child_dictionary ($p_lang)
// Synchronize child language dictionary with parent's
// This will replace child dictionary with parent's (but transcribing it)
//
function sync_child_dictionary ($p_lang) {
//global $lang_defs;
if ($this->lang_defs[$p_lang]['parent']
and $this->language_defined($this->lang_defs[$p_lang]['parent']))
{
$this->lang_defs[$p_lang]['dictionary'] = $this->lang_defs[$this->lang_defs[$p_lang]['parent']]['dictionary'];
$this->transcribe_parent_language_dictionary ($p_lang);
}
}
//
// is_family ($p_lang, $p_parent)
// Check if language belongs to the family of languages (is parent or one of languages with same parent)
//
function is_family ($p_lang, $p_parent) {
//global $lang_defs;
$m_lang = $this->lang_defs[$p_lang];
return ($m_lang['id'] == $p_parent) or ($m_lang['parent'] == $p_parent);
}
//
// get_family ($p_lang)
// Return family which language belongs to
//
function get_family ($p_lang) {
//global $lang_defs;
$m_lang = $this->lang_defs[$p_lang];
if ($m_lang['parent']) {
return $m_lang['parent'];
} else {
return $m_lang['id'];
}
}
//
// block_start()
// Begin output buffering. All output after this will be buffered so language replacements may be made.
//
function block_start() {
ob_start();
}
//
// block_end()
// End output buffering, do necessary language processing and display altered document.
//
function block_end() {
$page_contents = ob_get_contents();
ob_end_clean();
echo $this->process ($page_contents);
}
} // class ULT
?>