diff --git a/.gitignore b/.gitignore index 3bdc4e066866d0433aafc3341fa4b43b0b420b58..6e7535b0b92c9fd5d8c741d3c95ba4fe7c19630e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ Vagrantfile # Staging directory used for packaging script stage + +# Ignore packaged plugins and language packs +*.phar diff --git a/include/ajax.tips.php b/include/ajax.tips.php index ed560403e945933600ad3390dea1aa508530ea8b..e81d1301b53622478520050bcd6e75528b28e36e 100644 --- a/include/ajax.tips.php +++ b/include/ajax.tips.php @@ -20,8 +20,13 @@ if(!defined('INCLUDE_DIR')) die('!'); require_once(INCLUDE_DIR.'class.i18n.php'); class HelpTipAjaxAPI extends AjaxController { - function getTipsJson($namespace, $lang='en_US') { - global $ost; + function getTipsJson($namespace, $lang=false) { + global $ost, $thisstaff; + + if (!$lang) + $lang = ($thisstaff) + ? $thisstaff->getLanguage() + : Internationalization::getDefaultLanguage(); $i18n = new Internationalization($lang); $tips = $i18n->getTemplate("help/tips/$namespace.yaml"); diff --git a/include/class.config.php b/include/class.config.php index 06607fdc52393e084f8fdc9dde376b4c8108a1e1..f5c7077d7dcbe07cddca31d10e83d1e63a7cb32e 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -148,6 +148,7 @@ class OsticketConfig extends Config { 'allow_online_attachments_onlogin' => false, 'name_format' => 'full', # First Last 'auto_claim_tickets'=> true, + 'system_language' => 'en_US', ); function OsticketConfig($section=null) { @@ -709,6 +710,10 @@ class OsticketConfig extends Config { return ($this->allowAttachments() && $this->get('allow_email_attachments')); } + function getSystemLanguage() { + return $this->get('system_language'); + } + //TODO: change db field to allow_api_attachments - which will include email/json/xml attachments // terminology changed on the UI function allowAPIAttachments() { diff --git a/include/class.i18n.php b/include/class.i18n.php index 7891d7ef937ecacf2bc148ae4a4e441801a8e003..fdb0061bb760da7e3cc8bd7364df70deff1cc0e1 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -16,7 +16,6 @@ **********************************************************************/ require_once INCLUDE_DIR.'class.error.php'; require_once INCLUDE_DIR.'class.yaml.php'; -require_once INCLUDE_DIR.'class.config.php'; class Internationalization { @@ -43,7 +42,6 @@ class Internationalization { function loadDefaultData() { # notrans -- do not translate the contents of this array $models = array( - 'email_template_group.yaml' => 'EmailTemplateGroup', 'department.yaml' => 'Dept', 'sla.yaml' => 'SLA', 'form.yaml' => 'DynamicForm', @@ -77,6 +75,7 @@ class Internationalization { } // Configuration + require_once INCLUDE_DIR.'class.config.php'; if (($tpl = $this->getTemplate('config.yaml')) && ($data = $tpl->getData())) { foreach ($data as $section=>$items) { @@ -101,6 +100,8 @@ class Internationalization { if (db_query($sql) && ($id = db_insert_id())) $_config->set("{$type}_page_id", $id); } + // Default Language + $_config->set('system_language', $this->langs[0]); // Canned response examples if (($tpl = $this->getTemplate('templates/premade.yaml')) @@ -118,6 +119,13 @@ class Internationalization { // Email templates // TODO: Lookup tpl_id + if ($objects = $this->getTemplate('email_template_group.yaml')->getData()) { + foreach ($objects as $o) { + $o['lang_id'] = $this->langs[0]; + $tpl = EmailTemplateGroup::create($o, $errors); + } + } + // This shouldn't be necessary $tpl = EmailTemplateGroup::lookup(1); foreach ($tpl->all_names as $name=>$info) { if (($tp = $this->getTemplate("templates/email/$name.yaml")) @@ -131,6 +139,120 @@ class Internationalization { } } } + + static function availableLanguages($base=I18N_DIR) { + $langs = (include I18N_DIR . 'langs.php'); + + // Consider all subdirectories and .phar files in the base dir + $dirs = glob(I18N_DIR . '*', GLOB_ONLYDIR | GLOB_NOSORT); + $phars = glob(I18N_DIR . '*.phar', GLOB_NOSORT); + + $installed = array(); + foreach (array_merge($dirs, $phars) as $f) { + $base = basename($f, '.phar'); + @list($code, $locale) = explode('_', $base); + if (isset($langs[$code])) { + $installed[strtolower($base)] = + $langs[$code] + array( + 'lang' => $code, + 'locale' => $locale, + 'path' => $f, + 'code' => $base, + 'desc' => sprintf("%s%s (%s)", + $langs[$code]['nativeName'], + $locale ? sprintf(' - %s', $locale) : '', + $langs[$code]['name']), + ); + } + } + + return $installed; + } + + // TODO: Move this to the REQUEST class or some middleware when that + // exists. + // Algorithm borrowed from Drupal 7 (locale.inc) + static function getDefaultLanguage() { + global $cfg; + + if (empty($_SERVER["HTTP_ACCEPT_LANGUAGE"])) + return $cfg->getSystemLanguage(); + + $languages = self::availableLanguages(); + + // The Accept-Language header contains information about the + // language preferences configured in the user's browser / operating + // system. RFC 2616 (section 14.4) defines the Accept-Language + // header as follows: + // Accept-Language = "Accept-Language" ":" + // 1#( language-range [ ";" "q" "=" qvalue ] ) + // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) + // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" + $browser_langcodes = array(); + if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', + trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + // We can safely use strtolower() here, tags are ASCII. + // RFC2616 mandates that the decimal part is no more than three + // digits, so we multiply the qvalue by 1000 to avoid floating + // point comparisons. + $langcode = strtolower($match[1]); + $qvalue = isset($match[2]) ? (float) $match[2] : 1; + $browser_langcodes[$langcode] = (int) ($qvalue * 1000); + } + } + + // We should take pristine values from the HTTP headers, but + // Internet Explorer from version 7 sends only specific language + // tags (eg. fr-CA) without the corresponding generic tag (fr) + // unless explicitly configured. In that case, we assume that the + // lowest value of the specific tags is the value of the generic + // language to be as close to the HTTP 1.1 spec as possible. + // + // References: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 + // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx + asort($browser_langcodes); + foreach ($browser_langcodes as $langcode => $qvalue) { + $generic_tag = strtok($langcode, '-'); + if (!isset($browser_langcodes[$generic_tag])) { + $browser_langcodes[$generic_tag] = $qvalue; + } + } + + // Find the enabled language with the greatest qvalue, following the rules + // of RFC 2616 (section 14.4). If several languages have the same qvalue, + // prefer the one with the greatest weight. + $best_match_langcode = FALSE; + $max_qvalue = 0; + foreach ($languages as $langcode => $language) { + // Language tags are case insensitive (RFC2616, sec 3.10). + // We use _ as the location separator + $langcode = str_replace('_','-',strtolower($langcode)); + + // If nothing matches below, the default qvalue is the one of the wildcard + // language, if set, or is 0 (which will never match). + $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0; + + // Find the longest possible prefix of the browser-supplied language + // ('the language-range') that matches this site language ('the language tag'). + $prefix = $langcode; + do { + if (isset($browser_langcodes[$prefix])) { + $qvalue = $browser_langcodes[$prefix]; + break; + } + } while ($prefix = substr($prefix, 0, strrpos($prefix, '-'))); + + // Find the best match. + if ($qvalue > $max_qvalue) { + $best_match_langcode = $language['code']; + $max_qvalue = $qvalue; + } + } + + return $best_match_langcode; + } } class DataTemplate { @@ -153,6 +275,12 @@ class DataTemplate { $this->filepath = realpath("{$this->base}/$l/$path"); break; } + elseif (Phar::isValidPharFilename("{$this->base}/$l.phar") + && file_exists("phar://{$this->base}/$l.phar/$path")) { + $this->lang = $l; + $this->filepath = "phar://{$this->base}/$l.phar/$path"; + break; + } } } diff --git a/include/class.staff.php b/include/class.staff.php index 3883ab3636b0d32dfb4b4b0108edb7e685cda6ce..efd4f341c03f40cb3fa79756cc038533b7c86c7f 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -65,6 +65,7 @@ class Staff extends AuthenticatedUser { $this->teams = $this->ht['teams'] = array(); $this->group = $this->dept = null; $this->departments = $this->stats = array(); + $this->config = new Config('staff.'.$this->id); //WE have to patch info here to support upgrading from old versions. if(($time=strtotime($this->ht['passwdreset']?$this->ht['passwdreset']:$this->ht['added']))) @@ -91,7 +92,7 @@ class Staff extends AuthenticatedUser { } function getInfo() { - return $this->getHastable(); + return $this->config->getInfo() + $this->getHastable(); } /*compares user password*/ @@ -255,6 +256,17 @@ class Staff extends AuthenticatedUser { return $this->dept; } + function getLanguage() { + static $cached = false; + if (!$cached) $cached = &$_SESSION['staff:lang']; + + if (!$cached) { + $cached = $this->config->get('lang'); + if (!$cached) + $cached = Internationalization::getDefaultLanguage(); + } + return $cached; + } function isManager() { return (($dept=$this->getDept()) && $dept->getManagerId()==$this->getId()); @@ -466,6 +478,9 @@ class Staff extends AuthenticatedUser { if($errors) return false; + $this->config->set('lang', $vars['lang']); + $_SESSION['staff:lang'] = null; + $sql='UPDATE '.STAFF_TABLE.' SET updated=NOW() ' .' ,firstname='.db_input($vars['firstname']) .' ,lastname='.db_input($vars['lastname']) diff --git a/include/class.template.php b/include/class.template.php index e135034cd4943d45190d70b710e9868f4df6a43f..70af7fce0d960ce494638398869ecfd34b858add 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -118,7 +118,7 @@ class EmailTemplateGroup { } function getLanguage() { - return 'en_US'; + return $this->ht['lang']; } function isInUse(){ @@ -320,6 +320,10 @@ class EmailTemplateGroup { .' ,isactive='.db_input($vars['isactive']) .' ,notes='.db_input(Format::sanitize($vars['notes'])); + if ($vars['lang_id']) + // TODO: Validation of lang_id + $sql .= ',lang='.db_input($vars['lang_id']); + if($id) { $sql='UPDATE '.EMAIL_TEMPLATE_GRP_TABLE.' SET '.$sql.' WHERE tpl_id='.db_input($id); if(db_query($sql)) diff --git a/include/i18n/en_US/help/tips/install.yaml b/include/i18n/en_US/help/tips/install.yaml index 2cb86b36dfeee5b7a282c2e797f4fd992fa06234..b65340da0096ba4152e4660bbc639b058a3b340f 100644 --- a/include/i18n/en_US/help/tips/install.yaml +++ b/include/i18n/en_US/help/tips/install.yaml @@ -20,6 +20,16 @@ system_email: <p>Default email address e.g support@yourcompany.com - you can add more later!</p> +default_lang: + title: Default System Language + content: | + <p>Initial data for this language will be installed into the + database. For instance email templates and default system pages will + be installed for this language.</p> + links: + - title: osTicket Language Packs + href: http://osticket.com/download?product=langs + first_name: title: First Name content: | diff --git a/include/i18n/langs.php b/include/i18n/langs.php new file mode 100644 index 0000000000000000000000000000000000000000..09957c42be514963c8d7a40f99051b996e728b42 --- /dev/null +++ b/include/i18n/langs.php @@ -0,0 +1,735 @@ +<?php +/** + * @author Phil Teare + * using wikipedia data + */ +return array( + "ab" => array( + "name" => "Abkhaz", + "nativeName" => "аҧÑуа" + ), + "aa" => array( + "name" => "Afar", + "nativeName" => "Afaraf" + ), + "af" => array( + "name" => "Afrikaans", + "nativeName" => "Afrikaans" + ), + "ak" => array( + "name" => "Akan", + "nativeName" => "Akan" + ), + "sq" => array( + "name" => "Albanian", + "nativeName" => "Shqip" + ), + "am" => array( + "name" => "Amharic", + "nativeName" => "አማáˆáŠ›" + ), + "ar" => array( + "name" => "Arabic", + "nativeName" => "العربية" + ), + "an" => array( + "name" => "Aragonese", + "nativeName" => "Aragonés" + ), + "hy" => array( + "name" => "Armenian", + "nativeName" => "Õ€Õ¡ÕµÕ¥Ö€Õ¥Õ¶" + ), + "as" => array( + "name" => "Assamese", + "nativeName" => "অসমীয়া" + ), + "av" => array( + "name" => "Avaric", + "nativeName" => "авар мацӀ, магӀарул мацӀ" + ), + "ae" => array( + "name" => "Avestan", + "nativeName" => "avesta" + ), + "ay" => array( + "name" => "Aymara", + "nativeName" => "aymar aru" + ), + "az" => array( + "name" => "Azerbaijani", + "nativeName" => "azÉ™rbaycan dili" + ), + "bm" => array( + "name" => "Bambara", + "nativeName" => "bamanankan" + ), + "ba" => array( + "name" => "Bashkir", + "nativeName" => "башҡорт теле" + ), + "eu" => array( + "name" => "Basque", + "nativeName" => "euskara, euskera" + ), + "be" => array( + "name" => "Belarusian", + "nativeName" => "БеларуÑкаÑ" + ), + "bn" => array( + "name" => "Bengali", + "nativeName" => "বাংলা" + ), + "bh" => array( + "name" => "Bihari", + "nativeName" => "à¤à¥‹à¤œà¤ªà¥à¤°à¥€" + ), + "bi" => array( + "name" => "Bislama", + "nativeName" => "Bislama" + ), + "bs" => array( + "name" => "Bosnian", + "nativeName" => "bosanski jezik" + ), + "br" => array( + "name" => "Breton", + "nativeName" => "brezhoneg" + ), + "bg" => array( + "name" => "Bulgarian", + "nativeName" => "българÑки език" + ), + "my" => array( + "name" => "Burmese", + "nativeName" => "ဗမာစာ" + ), + "ca" => array( + "name" => "Catalan; Valencian", + "nativeName" => "Català " + ), + "ch" => array( + "name" => "Chamorro", + "nativeName" => "Chamoru" + ), + "ce" => array( + "name" => "Chechen", + "nativeName" => "нохчийн мотт" + ), + "ny" => array( + "name" => "Chichewa; Chewa; Nyanja", + "nativeName" => "chiCheŵa, chinyanja" + ), + "zh" => array( + "name" => "Chinese", + "nativeName" => "䏿–‡ (ZhÅngwén), 汉è¯, 漢語" + ), + "cv" => array( + "name" => "Chuvash", + "nativeName" => "чӑваш чӗлхи" + ), + "kw" => array( + "name" => "Cornish", + "nativeName" => "Kernewek" + ), + "co" => array( + "name" => "Corsican", + "nativeName" => "corsu, lingua corsa" + ), + "cr" => array( + "name" => "Cree", + "nativeName" => "á“€á¦áƒá”ááá£" + ), + "hr" => array( + "name" => "Croatian", + "nativeName" => "hrvatski" + ), + "cs" => array( + "name" => "Czech", + "nativeName" => "Äesky, ÄeÅ¡tina" + ), + "da" => array( + "name" => "Danish", + "nativeName" => "dansk" + ), + "dv" => array( + "name" => "Divehi; Dhivehi; Maldivian;", + "nativeName" => "Þ‹Þ¨ÞˆÞ¬Þ€Þ¨" + ), + "nl" => array( + "name" => "Dutch", + "nativeName" => "Nederlands, Vlaams" + ), + "en" => array( + "name" => "English", + "nativeName" => "English" + ), + "eo" => array( + "name" => "Esperanto", + "nativeName" => "Esperanto" + ), + "et" => array( + "name" => "Estonian", + "nativeName" => "eesti, eesti keel" + ), + "ee" => array( + "name" => "Ewe", + "nativeName" => "EÊ‹egbe" + ), + "fo" => array( + "name" => "Faroese", + "nativeName" => "føroyskt" + ), + "fj" => array( + "name" => "Fijian", + "nativeName" => "vosa Vakaviti" + ), + "fi" => array( + "name" => "Finnish", + "nativeName" => "suomi, suomen kieli" + ), + "fr" => array( + "name" => "French", + "nativeName" => "français, langue française" + ), + "ff" => array( + "name" => "Fula; Fulah; Pulaar; Pular", + "nativeName" => "Fulfulde, Pulaar, Pular" + ), + "gl" => array( + "name" => "Galician", + "nativeName" => "Galego" + ), + "ka" => array( + "name" => "Georgian", + "nativeName" => "ქáƒáƒ თული" + ), + "de" => array( + "name" => "German", + "nativeName" => "Deutsch" + ), + "el" => array( + "name" => "Greek, Modern", + "nativeName" => "Ελληνικά" + ), + "gn" => array( + "name" => "GuaranÃ", + "nativeName" => "Avañeẽ" + ), + "gu" => array( + "name" => "Gujarati", + "nativeName" => "ગà«àªœàª°àª¾àª¤à«€" + ), + "ht" => array( + "name" => "Haitian; Haitian Creole", + "nativeName" => "Kreyòl ayisyen" + ), + "ha" => array( + "name" => "Hausa", + "nativeName" => "Hausa, Ù‡ÙŽÙˆÙØ³ÙŽ" + ), + "he" => array( + "name" => "Hebrew (modern)", + "nativeName" => "עברית" + ), + "hz" => array( + "name" => "Herero", + "nativeName" => "Otjiherero" + ), + "hi" => array( + "name" => "Hindi", + "nativeName" => "हिनà¥à¤¦à¥€, हिंदी" + ), + "ho" => array( + "name" => "Hiri Motu", + "nativeName" => "Hiri Motu" + ), + "hu" => array( + "name" => "Hungarian", + "nativeName" => "Magyar" + ), + "ia" => array( + "name" => "Interlingua", + "nativeName" => "Interlingua" + ), + "id" => array( + "name" => "Indonesian", + "nativeName" => "Bahasa Indonesia" + ), + "ie" => array( + "name" => "Interlingue", + "nativeName" => "Originally called Occidental; then Interlingue after WWII" + ), + "ga" => array( + "name" => "Irish", + "nativeName" => "Gaeilge" + ), + "ig" => array( + "name" => "Igbo", + "nativeName" => "Asụsụ Igbo" + ), + "ik" => array( + "name" => "Inupiaq", + "nativeName" => "Iñupiaq, Iñupiatun" + ), + "io" => array( + "name" => "Ido", + "nativeName" => "Ido" + ), + "is" => array( + "name" => "Icelandic", + "nativeName" => "Ãslenska" + ), + "it" => array( + "name" => "Italian", + "nativeName" => "Italiano" + ), + "iu" => array( + "name" => "Inuktitut", + "nativeName" => "áƒá“„ᒃᑎá‘ᑦ" + ), + "ja" => array( + "name" => "Japanese", + "nativeName" => "日本語 (ã«ã»ã‚“ã”ï¼ã«ã£ã½ã‚“ã”)" + ), + "jv" => array( + "name" => "Javanese", + "nativeName" => "basa Jawa" + ), + "kl" => array( + "name" => "Kalaallisut, Greenlandic", + "nativeName" => "kalaallisut, kalaallit oqaasii" + ), + "kn" => array( + "name" => "Kannada", + "nativeName" => "ಕನà³à²¨à²¡" + ), + "kr" => array( + "name" => "Kanuri", + "nativeName" => "Kanuri" + ), + "ks" => array( + "name" => "Kashmiri", + "nativeName" => "कशà¥à¤®à¥€à¤°à¥€, كشميري‎" + ), + "kk" => array( + "name" => "Kazakh", + "nativeName" => "Қазақ тілі" + ), + "km" => array( + "name" => "Khmer", + "nativeName" => "ភាសាážáŸ’មែរ" + ), + "ki" => array( + "name" => "Kikuyu, Gikuyu", + "nativeName" => "GÄ©kÅ©yÅ©" + ), + "rw" => array( + "name" => "Kinyarwanda", + "nativeName" => "Ikinyarwanda" + ), + "ky" => array( + "name" => "Kirghiz, Kyrgyz", + "nativeName" => "кыргыз тили" + ), + "kv" => array( + "name" => "Komi", + "nativeName" => "коми кыв" + ), + "kg" => array( + "name" => "Kongo", + "nativeName" => "KiKongo" + ), + "ko" => array( + "name" => "Korean", + "nativeName" => "한êµì–´ (韓國語), ì¡°ì„ ë§ (æœé®®èªž)" + ), + "ku" => array( + "name" => "Kurdish", + "nativeName" => "Kurdî, كوردی‎" + ), + "kj" => array( + "name" => "Kwanyama, Kuanyama", + "nativeName" => "Kuanyama" + ), + "la" => array( + "name" => "Latin", + "nativeName" => "latine, lingua latina" + ), + "lb" => array( + "name" => "Luxembourgish, Letzeburgesch", + "nativeName" => "Lëtzebuergesch" + ), + "lg" => array( + "name" => "Luganda", + "nativeName" => "Luganda" + ), + "li" => array( + "name" => "Limburgish, Limburgan, Limburger", + "nativeName" => "Limburgs" + ), + "ln" => array( + "name" => "Lingala", + "nativeName" => "Lingála" + ), + "lo" => array( + "name" => "Lao", + "nativeName" => "ພາສາລາວ" + ), + "lt" => array( + "name" => "Lithuanian", + "nativeName" => "lietuvių kalba" + ), + "lu" => array( + "name" => "Luba-Katanga", + "nativeName" => "" + ), + "lv" => array( + "name" => "Latvian", + "nativeName" => "latvieÅ¡u valoda" + ), + "gv" => array( + "name" => "Manx", + "nativeName" => "Gaelg, Gailck" + ), + "mk" => array( + "name" => "Macedonian", + "nativeName" => "македонÑки јазик" + ), + "mg" => array( + "name" => "Malagasy", + "nativeName" => "Malagasy fiteny" + ), + "ms" => array( + "name" => "Malay", + "nativeName" => "bahasa Melayu, بهاس ملايو‎" + ), + "ml" => array( + "name" => "Malayalam", + "nativeName" => "മലയാളം" + ), + "mt" => array( + "name" => "Maltese", + "nativeName" => "Malti" + ), + "mi" => array( + "name" => "MÄori", + "nativeName" => "te reo MÄori" + ), + "mr" => array( + "name" => "Marathi (MarÄá¹hÄ«)", + "nativeName" => "मराठी" + ), + "mh" => array( + "name" => "Marshallese", + "nativeName" => "Kajin M̧ajeļ" + ), + "mn" => array( + "name" => "Mongolian", + "nativeName" => "монгол" + ), + "na" => array( + "name" => "Nauru", + "nativeName" => "EkakairÅ© Naoero" + ), + "nv" => array( + "name" => "Navajo, Navaho", + "nativeName" => "Diné bizaad, DinékʼehǰÃ" + ), + "nb" => array( + "name" => "Norwegian BokmÃ¥l", + "nativeName" => "Norsk bokmÃ¥l" + ), + "nd" => array( + "name" => "North Ndebele", + "nativeName" => "isiNdebele" + ), + "ne" => array( + "name" => "Nepali", + "nativeName" => "नेपाली" + ), + "ng" => array( + "name" => "Ndonga", + "nativeName" => "Owambo" + ), + "nn" => array( + "name" => "Norwegian Nynorsk", + "nativeName" => "Norsk nynorsk" + ), + "no" => array( + "name" => "Norwegian", + "nativeName" => "Norsk" + ), + "ii" => array( + "name" => "Nuosu", + "nativeName" => "ê†ˆêŒ ê’¿ Nuosuhxop" + ), + "nr" => array( + "name" => "South Ndebele", + "nativeName" => "isiNdebele" + ), + "oc" => array( + "name" => "Occitan", + "nativeName" => "Occitan" + ), + "oj" => array( + "name" => "Ojibwe, Ojibwa", + "nativeName" => "áŠá“‚ᔑᓈá¯á’§áŽá“" + ), + "cu" => array( + "name" => "Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic", + "nativeName" => "ѩзыкъ ÑловѣньÑкъ" + ), + "om" => array( + "name" => "Oromo", + "nativeName" => "Afaan Oromoo" + ), + "or" => array( + "name" => "Oriya", + "nativeName" => "ଓଡ଼ିଆ" + ), + "os" => array( + "name" => "Ossetian, Ossetic", + "nativeName" => "ирон æвзаг" + ), + "pa" => array( + "name" => "Panjabi, Punjabi", + "nativeName" => "ਪੰਜਾਬੀ, پنجابی‎" + ), + "pi" => array( + "name" => "PÄli", + "nativeName" => "पाऴि" + ), + "fa" => array( + "name" => "Persian", + "nativeName" => "ÙØ§Ø±Ø³ÛŒ" + ), + "pl" => array( + "name" => "Polish", + "nativeName" => "polski" + ), + "ps" => array( + "name" => "Pashto, Pushto", + "nativeName" => "پښتو" + ), + "pt" => array( + "name" => "Portuguese", + "nativeName" => "Português" + ), + "qu" => array( + "name" => "Quechua", + "nativeName" => "Runa Simi, Kichwa" + ), + "rm" => array( + "name" => "Romansh", + "nativeName" => "rumantsch grischun" + ), + "rn" => array( + "name" => "Kirundi", + "nativeName" => "kiRundi" + ), + "ro" => array( + "name" => "Romanian, Moldavian, Moldovan", + "nativeName" => "română" + ), + "ru" => array( + "name" => "Russian", + "nativeName" => "руÑÑкий Ñзык" + ), + "sa" => array( + "name" => "Sanskrit (Saá¹ská¹›ta)", + "nativeName" => "संसà¥à¤•ृतमà¥" + ), + "sc" => array( + "name" => "Sardinian", + "nativeName" => "sardu" + ), + "sd" => array( + "name" => "Sindhi", + "nativeName" => "सिनà¥à¤§à¥€, سنڌي، سندھی‎" + ), + "se" => array( + "name" => "Northern Sami", + "nativeName" => "Davvisámegiella" + ), + "sm" => array( + "name" => "Samoan", + "nativeName" => "gagana faa Samoa" + ), + "sg" => array( + "name" => "Sango", + "nativeName" => "yângâ tî sängö" + ), + "sr" => array( + "name" => "Serbian", + "nativeName" => "ÑрпÑки језик" + ), + "gd" => array( + "name" => "Scottish Gaelic; Gaelic", + "nativeName" => "Gà idhlig" + ), + "sn" => array( + "name" => "Shona", + "nativeName" => "chiShona" + ), + "si" => array( + "name" => "Sinhala, Sinhalese", + "nativeName" => "සිංහල" + ), + "sk" => array( + "name" => "Slovak", + "nativeName" => "slovenÄina" + ), + "sl" => array( + "name" => "Slovene", + "nativeName" => "slovenÅ¡Äina" + ), + "so" => array( + "name" => "Somali", + "nativeName" => "Soomaaliga, af Soomaali" + ), + "st" => array( + "name" => "Southern Sotho", + "nativeName" => "Sesotho" + ), + "es" => array( + "name" => "Spanish; Castilian", + "nativeName" => "español, castellano" + ), + "su" => array( + "name" => "Sundanese", + "nativeName" => "Basa Sunda" + ), + "sw" => array( + "name" => "Swahili", + "nativeName" => "Kiswahili" + ), + "ss" => array( + "name" => "Swati", + "nativeName" => "SiSwati" + ), + "sv" => array( + "name" => "Swedish", + "nativeName" => "svenska" + ), + "ta" => array( + "name" => "Tamil", + "nativeName" => "தமிழà¯" + ), + "te" => array( + "name" => "Telugu", + "nativeName" => "తెలà±à°—à±" + ), + "tg" => array( + "name" => "Tajik", + "nativeName" => "тоҷикӣ, toÄŸikÄ«, تاجیکی‎" + ), + "th" => array( + "name" => "Thai", + "nativeName" => "ไทย" + ), + "ti" => array( + "name" => "Tigrinya", + "nativeName" => "ትáŒáˆáŠ›" + ), + "bo" => array( + "name" => "Tibetan Standard, Tibetan, Central", + "nativeName" => "བོད་ཡིག" + ), + "tk" => array( + "name" => "Turkmen", + "nativeName" => "Türkmen, Түркмен" + ), + "tl" => array( + "name" => "Tagalog", + "nativeName" => "Wikang Tagalog, áœáœ’ᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔" + ), + "tn" => array( + "name" => "Tswana", + "nativeName" => "Setswana" + ), + "to" => array( + "name" => "Tonga (Tonga Islands)", + "nativeName" => "faka Tonga" + ), + "tr" => array( + "name" => "Turkish", + "nativeName" => "Türkçe" + ), + "ts" => array( + "name" => "Tsonga", + "nativeName" => "Xitsonga" + ), + "tt" => array( + "name" => "Tatar", + "nativeName" => "татарча, tatarça, تاتارچا‎" + ), + "tw" => array( + "name" => "Twi", + "nativeName" => "Twi" + ), + "ty" => array( + "name" => "Tahitian", + "nativeName" => "Reo Tahiti" + ), + "ug" => array( + "name" => "Uighur, Uyghur", + "nativeName" => "UyÆ£urqÉ™, ئۇيغۇرچە‎" + ), + "uk" => array( + "name" => "Ukrainian", + "nativeName" => "українÑька" + ), + "ur" => array( + "name" => "Urdu", + "nativeName" => "اردو" + ), + "uz" => array( + "name" => "Uzbek", + "nativeName" => "zbek, Ўзбек, أۇزبÛك‎" + ), + "ve" => array( + "name" => "Venda", + "nativeName" => "Tshivenḓa" + ), + "vi" => array( + "name" => "Vietnamese", + "nativeName" => "Tiếng Việt" + ), + "vo" => array( + "name" => "Volapük", + "nativeName" => "Volapük" + ), + "wa" => array( + "name" => "Walloon", + "nativeName" => "Walon" + ), + "cy" => array( + "name" => "Welsh", + "nativeName" => "Cymraeg" + ), + "wo" => array( + "name" => "Wolof", + "nativeName" => "Wollof" + ), + "fy" => array( + "name" => "Western Frisian", + "nativeName" => "Frysk" + ), + "xh" => array( + "name" => "Xhosa", + "nativeName" => "isiXhosa" + ), + "yi" => array( + "name" => "Yiddish", + "nativeName" => "ייִדיש" + ), + "yo" => array( + "name" => "Yoruba", + "nativeName" => "Yorùbá" + ), + "za" => array( + "name" => "Zhuang, Chuang", + "nativeName" => "Saɯ cueŋƅ, Saw cuengh" + ) +); diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 8ceaca328452056ad3829a00ae69f64c85dae52e..116a39eab0617425965850687298f47999bfb77d 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -100,6 +100,24 @@ $info['id']=$staff->getId(); <span class="error">* <?php echo $errors['timezone_id']; ?></span> </td> </tr> + <tr> + <td width="180"> + Preferred Language: + </td> + <td> + <?php + $langs = Internationalization::availableLanguages(); ?> + <select name="lang"> + <option value="">— Use Browser Preference —</option> +<?php foreach($langs as $l) { + $selected = ($info['lang'] == $l['code']) ? 'selected="selected"' : ''; ?> + <option value="<?php echo $l['code']; ?>" <?php echo $selected; + ?>><?php echo $l['desc']; ?></option> +<?php } ?> + </select> + <span class="error"> <?php echo $errors['lang']; ?></span> + </td> + </tr> <tr> <td width="180"> Daylight Saving: diff --git a/include/staff/template.inc.php b/include/staff/template.inc.php index 8fee6a2ce6a8bae63c9273b99eafa178c48d0214..2d05235eecb07d2a20c5d389fc58b1aaf763a022 100644 --- a/include/staff/template.inc.php +++ b/include/staff/template.inc.php @@ -15,6 +15,7 @@ if($template && $_REQUEST['a']!='add'){ $action='add'; $submit_text='Add Template'; $info['isactive']=isset($info['isactive'])?$info['isactive']:0; + $info['lang_id'] = $cfg->getSystemLanguage(); $qstr.='&a='.urlencode($_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); @@ -54,19 +55,22 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <span class="error">* <?php echo $errors['isactive']; ?></span> </td> </tr> + <?php + if($template){ ?> <tr> <td width="180" class="required"> Language: </td> <td> - <select name="lang_id"> - <option value="en" selected="selected">English (US)</option> - </select> - <span class="error">* <?php echo $errors['lang_id']; ?></span> + <?php + $langs = Internationalization::availableLanguages(); + $lang = strtolower($info['lang']); + if (isset($langs[$lang])) + echo $langs[$lang]['desc']; + else + echo $info['lang']; ?> </td> </tr> - <?php - if($template){ ?> <tr> <th colspan="2"> <em><strong>Template Messages</strong>: Click on the message to edit. @@ -100,6 +104,23 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } } }else{ ?> + <tr> + <td width="180" class="required"> + Language: + </td> + <td> + <?php + $langs = Internationalization::availableLanguages(); ?> + <select name="lang_id"> +<?php foreach($langs as $l) { + $selected = ($info['lang_id'] == $l['code']) ? 'selected="selected"' : ''; ?> + <option value="<?php echo $l['code']; ?>" <?php echo $selected; + ?>><?php echo $l['desc']; ?></option> +<?php } ?> + </select> + <span class="error">* <?php echo $errors['lang_id']; ?></span> + </td> + </tr> <tr> <td width="180" class="required"> Template To Clone: diff --git a/scp/ajax.php b/scp/ajax.php index 01b41867dd53bc052d5a7b9c8765327b140e3739..7e990934b8abe59b1b9a1aecfd3ccd91cfb06271 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -101,8 +101,8 @@ $dispatcher = patterns('', )), url_post('^/upgrader', array('ajax.upgrader.php:UpgraderAjaxAPI', 'upgrade')), url('^/help/', patterns('ajax.tips.php:HelpTipAjaxAPI', - url_get('tips/(?P<namespace>[\w_.]+)$', 'getTipsJson'), - url_get('(?P<lang>\w{2}_\w{2})?/tips/(?P<namespace>[\w_.]+)$', 'getTipsForLangJson') + url_get('^tips/(?P<namespace>[\w_.]+)$', 'getTipsJson'), + url_get('^(?P<lang>[\w_]+)?/tips/(?P<namespace>[\w_.]+)$', 'getTipsJsonForLang') )) ); diff --git a/setup/ajax.php b/setup/ajax.php index 9c2c7b282a325f135f3dde4e8e80491860666b31..97e45daddc88ece9cd65e24ea71dfb9f0d7ac8b9 100644 --- a/setup/ajax.php +++ b/setup/ajax.php @@ -23,8 +23,8 @@ require_once INCLUDE_DIR.'/class.ajax.php'; $dispatcher = patterns('', url('^/help/', patterns('ajax.tips.php:HelpTipAjaxAPI', - url_get('tips/(?P<namespace>[\w_]+)$', 'getTipsJson'), - url_get('(?P<lang>\w{2}_\w{2})?/tips/(?P<namespace>[\w_]+)$', 'getTipsForLangJson') + url_get('^tips/(?P<namespace>[\w_.]+)$', 'getTipsJson'), + url_get('^(?P<lang>[\w_]+)?/tips/(?P<namespace>[\w_.]+)$', 'getTipsJsonForLang') )) ); print $dispatcher->resolve(Osticket::get_path_info()); diff --git a/setup/cli/cli.inc.php b/setup/cli/cli.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..31bdbfe8993cdadadbc4896c1e9257b3909b8cc0 --- /dev/null +++ b/setup/cli/cli.inc.php @@ -0,0 +1,31 @@ +<?php +/********************************************************************* + cli.inc.php + + Master include file which must be included at the start of every file. + This is a modification of main.inc.php to support running cli scripts. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +#Disable direct access. +if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('kwaheri rafiki!'); + +define('ROOT_PATH', '/'); +define('INC_DIR',dirname(__file__).'/../inc/'); //local include dir! + +require_once(dirname(__file__).'/../../bootstrap.php'); + +Bootstrap::loadConfig(); +Bootstrap::defineTables(TABLE_PREFIX); +Bootstrap::loadCode(); +Bootstrap::i18n_prep(); + +?> diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index 437f87c609eb6b63f6799978ce324f437a456ca5..421e49bd11a22f115af0e9d3c288099f07859e3f 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -227,6 +227,11 @@ class Module { function run($args, $options) { } + function fail($message) { + $this->stderr->write($message . "\n"); + die(); + } + /* static */ function register($action, $class) { global $registered_modules; diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php new file mode 100644 index 0000000000000000000000000000000000000000..a9a4117c4b15f59d6914dbd11edf741b1c5544b7 --- /dev/null +++ b/setup/cli/modules/i18n.php @@ -0,0 +1,130 @@ +<?php + +require_once dirname(__file__) . "/class.module.php"; +require_once dirname(__file__) . "/../cli.inc.php"; +require_once INCLUDE_DIR . 'class.format.php'; + +class i18n_Compiler extends Module { + + var $prologue = "Manages translation files from Crowdin"; + + var $arguments = array( + "command" => "Action to be performed. + list - Show list of available translations" + ); + + var $options = array( + "key" => array('-k','--key','metavar'=>'API-KEY', + 'help'=>'Crowdin project API key. This can be omitted if + CROWDIN_API_KEY is defined in the ost-config.php file'), + "lang" => array('-L', '--lang', 'metavar'=>'code', + 'help'=>'Language code (used for building)'), + ); + + static $crowdin_api_url = 'http://i18n.osticket.com/api/project/osticket-official/{command}'; + + function _http_get($url) { + #curl post + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_USERAGENT, 'osTicket/'.THIS_VERSION); + curl_setopt($ch, CURLOPT_HEADER, FALSE); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + $result=curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return array($code, $result); + } + + function _request($command, $args=array()) { + + $url = str_replace('{command}', $command, self::$crowdin_api_url); + + $args += array('key' => $this->key); + foreach ($args as &$a) + $a = urlencode($a); + unset($a); + $url .= '?' . Format::array_implode('=', '&', $args); + + return $this->_http_get($url); + } + + function run($args, $options) { + $this->key = $options['key']; + if (!$this->key && defined('CROWDIN_API_KEY')) + $this->key = CROWDIN_API_KEY; + + switch (strtolower($args['command'])) { + case 'list': + if (!$this->key) + $this->fail('API key is required'); + $this->_list(); + break; + case 'build': + if (!$this->key) + $this->fail('API key is required'); + if (!$options['lang']) + $this->fail('Language code is required. See `list`'); + $this->_build($options['lang']); + break; + } + } + + function _list() { + error_reporting(E_ALL); + list($code, $body) = $this->_request('status'); + $d = new DOMDocument(); + $d->loadXML($body); + + $xp = new DOMXpath($d); + foreach ($xp->query('//language') as $c) { + $name = $code = ''; + foreach ($c->childNodes as $n) { + switch (strtolower($n->nodeName)) { + case 'name': + $name = $n->textContent; + break; + case 'code': + $code = $n->textContent; + break; + } + } + if (!$code) + continue; + $this->stdout->write(sprintf("%s (%s)\n", $code, $name)); + } + } + + function _build($lang) { + list($code, $zip) = $this->_request("download/$lang.zip"); + + if ($code !== 200) + $this->fail('Language is not available'."\n"); + + $temp = tempnam('/tmp', 'osticket-cli'); + $f = fopen($temp, 'w'); + fwrite($f, $zip); + fclose($f); + $zip = new ZipArchive(); + $zip->open($temp); + unlink($temp); + + $lang = str_replace('-','_',$lang); + @unlink(I18N_DIR."$lang.phar"); + $phar = new Phar(I18N_DIR."$lang.phar"); + + for ($i=0; $i<$zip->numFiles; $i++) { + $info = $zip->statIndex($i); + $phar->addFromString($info['name'], $zip->getFromIndex($i)); + } + + // TODO: Add i18n extras (like fonts) + + // TODO: Sign files + } +} + +Module::register('i18n', 'i18n_Compiler'); +?> diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php index d57fa0b449095e9bb88a1cb3b9004dd0a1325c5a..4ec86eee5479e8902871bc1081f407e19deb3bda 100644 --- a/setup/inc/class.installer.php +++ b/setup/inc/class.installer.php @@ -15,6 +15,7 @@ **********************************************************************/ require_once INCLUDE_DIR.'class.migrater.php'; require_once INCLUDE_DIR.'class.setup.php'; +require_once INCLUDE_DIR.'class.i18n.php'; class Installer extends SetupWizard { @@ -149,10 +150,9 @@ class Installer extends SetupWizard { } if(!$this->errors) { - // TODO: Use language selected from install worksheet - require_once INCLUDE_DIR.'class.i18n.php'; - $i18n = new Internationalization('en_US'); + // TODO: Use language selected from install worksheet + $i18n = new Internationalization($vars['lang_id']); $i18n->loadDefaultData(); $sql='SELECT `id` FROM '.PREFIX.'sla ORDER BY `id` LIMIT 1'; diff --git a/setup/inc/install.inc.php b/setup/inc/install.inc.php index e2d6a5a0f3b87b28f7466a91780b2d5d932956fb..f58943a2fc450022077fd2d1ba59f752b636c66f 100644 --- a/setup/inc/install.inc.php +++ b/setup/inc/install.inc.php @@ -1,8 +1,8 @@ -<?php +<?php if(!defined('SETUPINC')) die('Kwaheri!'); -$info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbhost'=>'localhost'); +$info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbhost'=>'localhost','lang_id'=>'en_US'); ?> -<div id="main" class="step2"> +<div id="main" class="step2"> <h1>osTicket Basic Installation</h1> <p>Please fill out the information below to continue your osTicket installation. All fields are required.</p> <font class="error"><strong><?php echo $errors['err']; ?></strong></font> @@ -26,6 +26,19 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho <a class="tip" href="#system_email"><i class="icon-question-sign help-tip"></i></a> <font class="error"><?php echo $errors['email']; ?></font> </div> + <div class="row"> + <label>Default Language:</label> +<?php $langs = Internationalization::availableLanguages(); ?> + <select name="lang_id"> +<?php foreach($langs as $l) { + $selected = ($info['lang_id'] == $l['code']) ? 'selected="selected"' : ''; ?> + <option value="<?php echo $l['code']; ?>" <?php echo $selected; + ?>><?php echo $l['desc']; ?></option> +<?php } ?> + </select> + <a class="tip" href="#default_lang"><i class="icon-question-sign help-tip"></i></a> + <font class="error"> <?php echo $errors['lang_id']; ?></font> + </div> <h4 class="head admin">Admin User</h4> <span class="subhead">Your primary administrator account - you can add more users later.</span>