summaryrefslogtreecommitdiff
path: root/core/libraries/I18n.php
diff options
context:
space:
mode:
Diffstat (limited to 'core/libraries/I18n.php')
-rw-r--r--core/libraries/I18n.php186
1 files changed, 56 insertions, 130 deletions
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