summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/helpers/core_installer.php21
-rw-r--r--core/libraries/I18n.php186
-rw-r--r--core/models/translations_incoming.php21
-rw-r--r--core/tests/I18n_Test.php37
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() {