diff options
author | Andy Staudacher <andy.st@gmail.com> | 2009-01-08 06:52:18 +0000 |
---|---|---|
committer | Andy Staudacher <andy.st@gmail.com> | 2009-01-08 06:52:18 +0000 |
commit | 2917740ba6d210ef43d4de228f8386e3b28adf62 (patch) | |
tree | 596abbb05c57dd0f017727741dc38a1545125be7 | |
parent | e9d61d5f9df80e4304abf60f5dde99b54eefad71 (diff) |
First step of i18n refactoring:
- Using DB table translations_incomings as translations storage (file cache to be added)
- Removed overly complex i18n code which will be unnecessary with the future compiled cache files
- Added t() as a translation function (global refactoring from _() to t() to follow)
-rw-r--r-- | core/helpers/core_installer.php | 21 | ||||
-rw-r--r-- | core/libraries/I18n.php | 186 | ||||
-rw-r--r-- | core/models/translations_incoming.php | 21 | ||||
-rw-r--r-- | core/tests/I18n_Test.php | 37 |
4 files changed, 116 insertions, 149 deletions
diff --git a/core/helpers/core_installer.php b/core/helpers/core_installer.php index d02d8465..15b97809 100644 --- a/core/helpers/core_installer.php +++ b/core/helpers/core_installer.php @@ -120,6 +120,18 @@ class core_installer { UNIQUE KEY(`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + $db->query("CREATE TABLE `translations_incomings` ( + `id` int(9) NOT NULL auto_increment, + `key` char(32) NOT NULL, + `locale` char(10) NOT NULL, + `message` text NOT NULL, + `translation` text, + `revision` int(9) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`, `locale`), + KEY `locale_key` (`locale`, `key`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + $db->query("CREATE TABLE `sessions` ( `session_id` varchar(127) NOT NULL, `last_activity` int(10) UNSIGNED NOT NULL, @@ -217,14 +229,13 @@ class core_installer { $db->query("DROP TABLE IF EXISTS `logs`;"); $db->query("DROP TABLE IF EXISTS `messages`;"); $db->query("DROP TABLE IF EXISTS `modules`;"); + $db->query("DROP TABLE IF EXISTS `translations_incoming`;"); $db->query("DROP TABLE IF EXISTS `permissions`;"); $db->query("DROP TABLE IF EXISTS `sessions`;"); $db->query("DROP TABLE IF EXISTS `tasks`;"); $db->query("DROP TABLE IF EXISTS `vars`;"); - system("/bin/rm -rf " . VARPATH . "albums"); - system("/bin/rm -rf " . VARPATH . "resizes"); - system("/bin/rm -rf " . VARPATH . "thumbs"); - system("/bin/rm -rf " . VARPATH . "uploads"); - system("/bin/rm -rf " . VARPATH . "modules"); + foreach (array("albums", "resizes", "thumbs", "uploads", "modules") as $dir) { + system("/bin/rm -rf " . VARPATH . $dir); + } } } diff --git a/core/libraries/I18n.php b/core/libraries/I18n.php index 44aad1e8..786274dd 100644 --- a/core/libraries/I18n.php +++ b/core/libraries/I18n.php @@ -19,33 +19,39 @@ */ /** - * @todo Add caching: e.g. keep all translation data in memory during the request. - * Remember the locale fallback, cache by locale - * @todo Might compile l10n files such that no fallbacks have to be performed. - * @todo Might keep all l10n data in the database instead of php files. + * @see I18n_Core::translate($message, $options) */ +function t($message, $options=array()) { + return I18n::instance()->translate($message, $options); +} + class I18n_Core { private $_config = array(); - private $_data = array(); - + private static $_instance; - - public $missing_placeholder_strategy; - + private function __construct($config) { $this->_config = $config; } - + public static function instance($config=null) { if (self::$_instance == NULL || isset($config)) { $config = isset($config) ? $config : Kohana::config('locale'); self::$_instance = new I18n_Core($config); - self::$_instance->missing_placeholder_strategy = new Ignore_Missing_Placeholder(); } return self::$_instance; } + /** + * Translates a localizable message. + * @param $message String|array The message to be translated. E.g. "Hello world" + * or array("one" => "One album", "other" => "{{count}} albums") + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special keys are "count" and "locale", + * the latter to override the currently configured locale. + * @return String The translated message string. + */ public function translate($message, $options=array() /** @todo , $hint=null */) { $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale']; $count = empty($options['count']) ? null : $options['count']; @@ -53,95 +59,63 @@ class I18n_Core { unset($values['locale']); $entry = $this->lookup($locale, $message); - + if (empty($entry)) { - $entry = $this->default_entry($message); + // Default to the root locale. + $entry = $message; + $locale = $this->_config['root_locale']; } $entry = $this->pluralize($locale, $entry, $count); - + $entry = $this->interpolate($locale, $entry, $values); - + return $entry; } - private function get_fallbacks_for_locale($locale) { - $fallbacks = array(); - $fallbacks[$locale] = true; - /** @todo add proper / robust locale string handling */ - /** @todo add smart locale fallback handling, e.g. en_US -> en -> en_* -> root */ - $locale_parts = explode('_', $locale); - if (count($locale_parts) == 2) { - $fallbacks[$locale_parts[0]] = true; - } - $fallbacks[$this->_config['default_locale']] = true; - - return array_keys($fallbacks); - } - private function lookup($locale, $message) { - $entry = null; - $locales = $this->get_fallbacks_for_locale($locale); + // TODO: Load data from locale file instead of the DB. + // If message is an array (plural forms), use the first form as message id. - // TODO: Might rather use hash of message as msgid. $key = is_array($message) ? array_shift($message) : $message; + $entry = Database::instance() + ->select("translation") + ->from("translations_incomings") + ->where(array("key" => md5($key), + "locale" => $locale)) + ->limit(1) + ->get() + ->current(); - while (!empty($locales) && $entry == null) { - $locale = array_shift($locales); - - if ($this->has_l10n_for_locale($locale)) { - $entry = $this->get_entry_from_locale_data($locale, $key); - } - } - - return $entry; - } - - private function default_entry($message) { - return $message; - } - - private function get_entry_from_locale_data($locale, $key) { - if (!isset($this->_data[$locale])) { - $this->load_l10n_data_for_locale($locale); - } - - if (isset($this->_data[$locale][$key])) { - return $this->_data[$locale][$key]; + if ($entry) { + return unserialize($entry->translation); + } else { + return null; } - - return null; } - - private function load_l10n_data_for_locale($locale) { - $data = array(); - include($this->_config['locale_dir'] . $locale . '.php'); - $this->_data[$locale] = $data; - } - private function interpolate($locale, $string, $values) { - // TODO: Benchmark whether {{stuff}} type syntax is prohibitively slow compared to sprintf()/ - // TODO: Benchmark whether str_replace() is much faster (no handling of escape syntax) - // TODO: Benchmark whether nested vs. outer function is significantly slower. - $callback = new I18n_Placeholder_Replacer($values, $locale, $string); - // TODO: Benchmark with pattern string as class constant - $string = preg_replace_callback("/(\\\\)?\{\{([^\}]+)\}\}/S", array($callback, 'replace'), $string); - - return $string; + // TODO: Handle locale specific number formatting. + $keys = array(); + foreach (array_keys($values) as $key) { + $keys[] = "{{" . $key . "}}"; + } + return str_replace($keys, array_values($values), $string); } - + private function pluralize($locale, $entry, $count) { - if ($count == NULL || !is_array($entry)) { + if (!is_array($entry)) { return $entry; + } else if ($count == null) { + $count = 1; } + $plural_key = self::get_plural_key($locale, $count); - if (!isset($entry[$plural_key])) { // Fallback to the default plural form. $plural_key = 'other'; } - + if (isset($entry[$plural_key])) { return $entry[$plural_key]; } else { @@ -150,15 +124,11 @@ class I18n_Core { return $string; } } - - private function has_l10n_for_locale($locale) { - return file_exists($this->_config['locale_dir'] . $locale . '.php'); - } - + private static function get_plural_key($locale, $count) { $parts = explode('_', $locale); $language = $parts[0]; - + // Data from CLDR 1.6 (http://unicode.org/cldr/data/common/supplemental/plurals.xml). // Docs: http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html switch ($language) { @@ -292,7 +262,7 @@ class I18n_Core { return 'few'; } else { return 'other'; - } + } case 'pl': if ($count == 1) { @@ -302,7 +272,7 @@ class I18n_Core { return 'few'; } else { return 'other'; - } + } case 'sl': if ($count % 100 == 1) { @@ -313,7 +283,7 @@ class I18n_Core { return 'few'; } else { return 'other'; - } + } case 'mt': if ($count == 1) { @@ -324,14 +294,14 @@ class I18n_Core { return 'many'; } else { return 'other'; - } + } case 'mk': if ($count % 10 == 1) { return 'one'; } else { return 'other'; - } + } case 'cy': if ($count == 1) { @@ -348,48 +318,4 @@ class I18n_Core { return $count == 1 ? 'one' : 'other'; } } -} - -class I18n_Placeholder_Replacer { - private $_values; - private $_locale; - private $_string; - - public function __construct($values, $locale, $string) { - $this->_values = $values; - $this->_locale = $locale; - $this->_string = $string; - } - - function replace($matches) { - list ($full_match, $escaped, $placeholder) = $matches; - - if ($escaped) { - return $full_match; - } else if (!isset($this->_values[$placeholder])) { - return I18n::instance()->missing_placeholder_strategy - ->replace($this->_locale, $this->_string, $this->_values, $placeholder, $full_match); - } else { - return $this->_values[$placeholder]; - } - } -} - -interface Missing_Placeholder_Strategy { - /** - * Handle the case where a localization requests a placeholder which is not provided in the translate() call. - * @param $locale The locale for this localization. - * @param String $string The complete message string. - * @param array $values All available replacement key value pairs. - * @param String $placeholder The placeholder for which there is no replacement value, e.g. "name" - * @param String $full_match The placeholder including its surrounding placeholder syntax, e.g. "{{name}}" - * @return String The replacement for the placeholder. - */ - public function replace($locale, $string, $values, $placeholder, $full_match); -} - -class Ignore_Missing_Placeholder implements Missing_Placeholder_Strategy { - function replace($locale, $string, $values, $placeholder, $full_match) { - return $full_match; - } }
\ No newline at end of file diff --git a/core/models/translations_incoming.php b/core/models/translations_incoming.php new file mode 100644 index 00000000..4dc8d2dd --- /dev/null +++ b/core/models/translations_incoming.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2008 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Translations_Incoming_Model extends ORM {
+}
diff --git a/core/tests/I18n_Test.php b/core/tests/I18n_Test.php index fab3cf43..2a13189a 100644 --- a/core/tests/I18n_Test.php +++ b/core/tests/I18n_Test.php @@ -24,24 +24,33 @@ class I18n_Test extends Unit_Test_Case { public function setup() { $config = array( 'root_locale' => 'en', - 'default_locale' => 'de_DE', + 'default_locale' => 'te_ST', 'locale_dir' => VARPATH . 'locale/'); $this->i18n = I18n::instance($config); - - $locale_file_contents = <<<EOT -<?php defined("SYSPATH") or die("No direct script access."); -\$data = array( - 'Hello world' => 'Hallo Welt', - 'One item has been added' => - array('one' => 'Ein Element wurde hinzugefuegt.', - 'other' => '{{count}} Elemente wurden hinzugefuegt.'), - 'Hello {{name}}, how are you today?' => 'Hallo {{name}}, wie geht es Dir heute?' -); -EOT; + $db = Database::instance(); + $db->query("DELETE FROM `translations_incomings` WHERE `locale` = 'te_ST'"); + + $messages_de_DE = array( + array('Hello world', 'Hallo Welt'), + array(array('one' => 'One item has been added', + 'other' => '{{count}} elements have been added'), + array('one' => 'Ein Element wurde hinzugefuegt.', + 'other' => '{{count}} Elemente wurden hinzugefuegt.')), + array('Hello {{name}}, how are you today?', 'Hallo {{name}}, wie geht es Dir heute?')); - @mkdir(VARPATH . 'locale'); - $fp = file_put_contents(VARPATH . 'locale/de_DE.php', $locale_file_contents); + foreach ($messages_de_DE as $data) { + list ($message, $translation) = $data; + $key = $message; + $key = is_array($key) ? array_shift($key) : $key; + $entry = ORM::factory("translations_incoming"); + $entry->key = md5($key); + $entry->message = serialize($message); + $entry->translation = serialize($translation); + $entry->locale = 'te_ST'; + $entry->revision = null; + $entry->save(); + } } public function translate_simple_test() { |