summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/controllers/l10n_client.php80
-rw-r--r--core/css/l10n_client.css184
-rw-r--r--core/helpers/core_theme.php13
-rw-r--r--core/js/l10n_client.js193
-rw-r--r--core/libraries/I18n.php48
-rw-r--r--core/tests/I18n_Test.php7
-rw-r--r--core/views/l10n_client.html.php30
-rw-r--r--core/views/scaffold.html.php7
8 files changed, 553 insertions, 9 deletions
diff --git a/core/controllers/l10n_client.php b/core/controllers/l10n_client.php
new file mode 100644
index 00000000..fb008ce0
--- /dev/null
+++ b/core/controllers/l10n_client.php
@@ -0,0 +1,80 @@
+<?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 L10n_Client_Controller extends Controller {
+ public function save($string) {
+ access::verify_csrf();
+
+ print json_encode(new stdClass());
+ }
+
+ private static function _l10n_client_form() {
+ $form = new Forge("/l10n_client/save", "", "post", array("id" => "gL10nClientSaveForm"));
+ $group = $form->group("l10n_message");
+ $group->textarea("l10n-edit-target");
+ $group->submit("l10n-edit-save")->value(t("Save translation"));
+ // TODO(andy_st): Avoiding multiple submit buttons for now (hassle with jQuery form plugin).
+ // $group->submit("l10n-edit-copy")->value(t("Copy source"));
+ // $group->submit("l10n-edit-clear")->value(t("Clear"));
+
+ return $form;
+ }
+
+ private static function _l10n_client_search_form() {
+ $form = new Forge("/l10n_client/search", "", "post", array("id" => "gL10nSearchForm"));
+ $group = $form->group("l10n_search");
+ $group->input("l10n-search")->id("gL10nSearch");
+ $group->submit("l10n-search-filter-clear")->value(t("X"));
+
+ return $form;
+ }
+
+ public static function l10n_form() {
+ $calls = I18n::instance()->getCallLog();
+
+ if ($calls) {
+ $string_list = array();
+ foreach ($calls as $call) {
+ list ($message, $options) = $call;
+ if (is_array($message)) {
+ // TODO: Translate each message. If it has a plural form, get
+ // the current locale's plural rules and all plural translations.
+ $options['count'] = 1;
+ $source = $message['one'];
+ } else {
+ $source = $message;
+ }
+ $translation = '';
+ if (I18n::instance()->hasTranslation($message, $options)) {
+ $translation = I18n::instance()->hasTtranslation($message, $options);
+ }
+ $string_list[] = array('source' => $source,
+ 'translation' => $translation);
+ }
+
+ return View::factory('l10n_client.html',
+ array('string_list' => $string_list,
+ 'l10n_form' => self::_l10n_client_form(),
+ 'l10n_search_form' => self::_l10n_client_search_form()))
+ ->render();
+ }
+
+ return '';
+ }
+}
diff --git a/core/css/l10n_client.css b/core/css/l10n_client.css
new file mode 100644
index 00000000..9e0b03d5
--- /dev/null
+++ b/core/css/l10n_client.css
@@ -0,0 +1,184 @@
+// TODO(andy_st): Add original copyright notice from Drupal l10_client.
+// TODO(andy_st): Add G3 copyright notice.
+
+/* $Id: l10n_client.css,v 1.6 2008/09/09 10:48:20 goba Exp $ */
+
+/* width percentages add to 99% rather than 100% to prevent float
+overflows from occurring in an unnamed browser that can't decide
+how it wants to round. */
+
+/* l10n_client container */
+#l10n-client {
+ text-align:left;
+ z-index:99;
+ line-height:1em;
+ color:#000; background:#fff;
+ position:fixed;
+ width:100%; height: 2em;
+ bottom:0px; left:0px;
+ overflow:hidden;}
+
+ * html #l10n-client {
+ position:static;}
+
+#l10n-client-string-select .string-list,
+#l10n-client-string-editor .source,
+#l10n-client-string-editor .editor {
+ height:20em;}
+
+#l10n-client .labels {
+ overflow:hidden;
+ position:relative;
+ height:2em;
+ color:#fff;
+ background:#37a;}
+
+ #l10n-client .labels .label {
+ display:none;}
+
+ /* Panel toggle button (span) */
+ #l10n-client .labels .toggle {
+ cursor:pointer;
+ display:block;
+ position:absolute; right:0em;
+ padding: 0em .75em; height:2em; line-height:2em;
+ text-transform:uppercase;
+ text-align:center; background:#000;}
+
+ /* Panel labels */
+ #l10n-client h2 {
+ border-left:1px solid #fff;
+ height:1em; line-height:1em;
+ padding: .5em; margin:0px;
+ font-size:1em;
+ text-transform:uppercase;}
+
+ #l10n-client .strings h2 {
+ border:0px;}
+
+ /* 25 + 37 + 37 = 99 */
+ #l10n-client .strings {
+ width:25%; float:left;}
+
+ #l10n-client .source {
+ width:37%; float:left;}
+
+ #l10n-client .translation {
+ width:37%; float:left;}
+
+/* Translatable string list */
+#l10n-client-string-select {
+ display:none;
+ float:left;
+ width:25%;}
+
+ #l10n-client .string-list {
+ height:17em;
+ overflow:auto;
+ list-style:none; list-style-image:none;
+ margin:0em; padding:0em;}
+
+ #l10n-client .string-list li {
+ font-size:.9em;
+ line-height:1.5em;
+ cursor:default;
+ background:transparent;
+ list-style:none; list-style-image:none;
+ border-bottom:1px solid #ddd;
+ padding:.25em .5em;
+ margin:0em;}
+
+ /* Green for translated */
+ #l10n-client .string-list li.translated {
+ border-bottom-color:#9c3;
+ background:#cf6; color:#360;}
+
+ #l10n-client .string-list li.translated:hover {
+ background: #df8;}
+
+ #l10n-client .string-list li.translated:active {
+ background: #9c3;}
+
+ #l10n-client .string-list li.hidden {
+ display:none;}
+
+ /* Gray + Blue hover for untranslated */
+ #l10n-client .string-list li.untranslated {}
+
+ #l10n-client .string-list li.untranslated:hover {
+ background: #ace;}
+
+ #l10n-client .string-list li.untranslated:active {
+ background: #8ac;}
+
+ /* Selected string is indicated by bold text */
+ #l10n-client .string-list li.active {
+ font-weight:bold;}
+
+ #l10n-client #gL10nSearchForm {
+ background:#eee;
+ text-align:center;
+ height:2em; line-height:2em;
+ margin:0em; padding:.5em .5em;
+ }
+
+ #l10n-client #gL10nSearchForm .form-item,
+ #l10n-client #gL10nSearchForm input.form-text,
+ #l10n-client #gL10nSearchForm #search-filter-go,
+ #l10n-client #gL10nSearchForm #search-filter-clear {
+ display:inline;
+ vertical-align:middle;
+ }
+
+ #l10n-client #gL10nSearchForm .form-item {
+ margin:0em;
+ padding:0em;
+ }
+
+ #l10n-client #gL10nSearchForm input.form-text {
+ width:80%;
+ }
+
+ #l10n-client #gL10nSearchForm #search-filter-clear {
+ width:10%;
+ margin:0em;
+ }
+
+
+#l10n-client-string-editor {
+ display:none;
+ float:left;
+ width:74%;}
+
+ #l10n-client-string-editor .source {
+ overflow:hidden;
+ width:50%; float:left;}
+
+ #l10n-client-string-editor .source .source-text {
+ line-height:1.5em;
+ background:#eee;
+ height:16em; margin:1em; padding:1em;
+ overflow:auto;}
+
+ #l10n-client-string-editor .translation {
+ overflow:hidden;
+ width:49%; float:right;}
+
+#gL10nClientSaveForm {
+ padding:0em;}
+
+ #gL10nClientSaveForm .form-textarea {
+ height:13em;
+ font-size:1em; line-height:1.25em;
+ width:95%;}
+
+ #gL10nClientSaveForm .form-submit {
+ margin-top: 0em;}
+
+
+#l10n-client form ul,
+#l10n-client form li,
+#l10n-client form input[type=submit],
+#l10n-client form input[type=text] {
+ display: inline ! important ;
+} \ No newline at end of file
diff --git a/core/helpers/core_theme.php b/core/helpers/core_theme.php
index 72e7829e..1e1f08d5 100644
--- a/core/helpers/core_theme.php
+++ b/core/helpers/core_theme.php
@@ -39,6 +39,14 @@ class core_theme_Core {
"</script>";
$buf .= html::script("core/js/fullsize.js");
}
+
+ if (Session::instance()->get("l10n_mode", false)) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("core/css/l10n_client.css") . "\" />";
+ $buf .= html::script("lib/jquery.cookie.js");
+ $buf .= html::script("core/js/l10n_client.js");
+ }
+
return $buf;
}
@@ -63,10 +71,15 @@ class core_theme_Core {
}
static function page_bottom($theme) {
+ $output = '';
+ if (Session::instance()->get("l10n_mode", false)) {
+ $output .= L10n_Client_Controller::l10n_form();
+ }
if (Session::instance()->get("profiler", false)) {
$profiler = new Profiler();
$profiler->render();
}
+ return $output;
}
static function admin_page_bottom($theme) {
diff --git a/core/js/l10n_client.js b/core/js/l10n_client.js
new file mode 100644
index 00000000..7d2ca5e3
--- /dev/null
+++ b/core/js/l10n_client.js
@@ -0,0 +1,193 @@
+// Fork from Drupal's l10n_client module, originally written by:
+// G‡bor Hojtsy http://drupal.org/user/4166 (original author)
+// Young Hahn / Development Seed - http://developmentseed.org/ (friendly user interface)
+
+var Gallery = Gallery || { 'behaviors': {} };
+
+Gallery.attachBehaviors = function(context) {
+ context = context || document;
+ // Execute all of them.
+ jQuery.each(Gallery.behaviors,
+ function() {
+ this(context);
+ });
+};
+
+$(document).ready(function() {
+ Gallery.attachBehaviors(this);
+});
+
+
+// Store all l10n_client related data + methods in its own object
+jQuery.extend(Gallery, {
+ l10nClient: new (function() {
+ // Set "selected" string to unselected, i.e. -1
+ this.selected = -1;
+ // Keybindings
+ this.keys = {'toggle':'ctrl+shift+s', 'clear': 'esc'}; // Keybindings
+ // Keybinding functions
+ this.key = function(pressed) {
+ switch(pressed) {
+ case 'toggle':
+ // Grab user-hilighted text & send it into the search filter
+ userSelection = window.getSelection ? window.getSelection() : document.getSelection ? document.getSelection() : document.selection.createRange().text;
+ userSelection = String(userSelection);
+ if(userSelection.length > 0) {
+ Gallery.l10nClient.filter(userSelection);
+ Gallery.l10nClient.toggle(1);
+ $('#l10n-client #gL10nSearch').focus();
+ } else {
+ if($('#l10n-client').is('.hidden')) {
+ Gallery.l10nClient.toggle(1);
+ if(!$.browser.safari) {
+ $('#l10n-client #gL10nSearch').focus();
+ }
+ } else {
+ Gallery.l10nClient.toggle(0);
+ }
+ }
+ break;
+ case 'clear':
+ this.filter(false);
+ break;
+ }
+ }
+ // Toggle the l10nclient
+ this.toggle = function(state) {
+ switch(state) {
+ case 1:
+ $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').show();
+ $('#l10n-client').height('22em').removeClass('hidden');
+ $('#l10n-client .labels .toggle').text('X');
+ /*
+ * This CSS clashes with Gallery's CSS, probably due to
+ * YUI's grid / floats.
+ if(!$.browser.msie) {
+ $('body').css('border-bottom', '22em solid #fff');
+ }
+ */
+ $.cookie('Gallery_l10n_client', '1', {expires: 7, path: '/'});
+ break;
+ case 0:
+ $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').hide();
+ $('#l10n-client').height('2em').addClass('hidden');
+ // TODO: Localize this message
+ $('#l10n-client .labels .toggle').text('Translate Text');
+ /*
+ if(!$.browser.msie) {
+ $('body').css('border-bottom', '0px');
+ }
+ */
+ $.cookie('Gallery_l10n_client', '0', {expires: 7, path: '/'});
+ break;
+ }
+ }
+ // Get a string from the DOM tree
+ this.getString = function(index, type) {
+ return l10n_client_data[index][type];
+ }
+ // Set a string in the DOM tree
+ this.setString = function(index, data) {
+ l10n_client_data[index]['translation'] = data;
+ }
+ // Filter the the string list by a search string
+ this.filter = function(search) {
+ if(search == false || search == '') {
+ $('#l10n-client #l10n-search-filter-clear').focus();
+ $('#l10n-client-string-select li').show();
+ $('#l10n-client #gL10nSearch').val('');
+ $('#l10n-client #gL10nSearch').focus();
+ } else {
+ if(search.length > 0) {
+ $('#l10n-client-string-select li').hide();
+ $('#l10n-client-string-select li:contains('+search+')').show();
+ $('#l10n-client #gL10nSearch').val(search);
+ }
+ }
+ }
+ })
+});
+
+// Attaches the localization editor behavior to all required fields.
+Gallery.behaviors.l10nClient = function(context) {
+
+ switch($.cookie('Gallery_l10n_client')) {
+ case '1':
+ Gallery.l10nClient.toggle(1);
+ break;
+ default:
+ Gallery.l10nClient.toggle(0);
+ break;
+ }
+
+ // If the selection changes, copy string values to the source and target fields.
+ // Add class to indicate selected string in list widget.
+ $('#l10n-client-string-select li').click(function() {
+ $('#l10n-client-string-select li').removeClass('active');
+ $(this).addClass('active');
+ var index = $('#l10n-client-string-select li').index(this);
+
+ $('#l10n-client-string-editor .source-text').text(Gallery.l10nClient.getString(index, 'source'));
+ $('#gL10nClientSaveForm #l10n-edit-target').val(Gallery.l10nClient.getString(index, 'translation'));
+
+ Gallery.l10nClient.selected = index;
+ });
+
+ // When l10n_client window is clicked, toggle based on current state.
+ $('#l10n-client .labels .toggle').click(function() {
+ if($('#l10n-client').is('.hidden')) {
+ Gallery.l10nClient.toggle(1);
+ } else {
+ Gallery.l10nClient.toggle(0);
+ }
+ });
+
+ // Register keybindings using jQuery hotkeys
+ // TODO: Either remove hotkeys code or add query.hotkeys.js.
+ if($.hotkeys) {
+ $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle')});
+ $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #gL10nSearch', type:'keyup'}, function(){Gallery.l10nClient.key('clear')});
+ }
+
+ // Custom listener for l10n_client livesearch
+ $('#l10n-client #gL10nSearch').keyup(function(key) {
+ Gallery.l10nClient.filter($('#l10n-client #gL10nSearch').val());
+ });
+
+ // Clear search
+ $('#l10n-client #l10n-search-filter-clear').click(function() {
+ Gallery.l10nClient.filter(false);
+ return false;
+ });
+
+ // Send AJAX POST data on form submit.
+ $('#gL10nClientSaveForm').ajaxForm({
+ dataType: "json",
+ success: function(data) {
+ // Store string in local js
+ Gallery.l10nClient.setString(Gallery.l10nClient.selected, $('#gL10nClientSaveForm #l10n-edit-target').val());
+
+ // Mark string as translated.
+ $('#l10n-client-string-select li').eq(Gallery.l10nClient.selected).removeClass('untranslated').removeClass('active').addClass('translated').text($('#gL10nClientSaveForm #l10n-edit-target').val());
+
+ // Empty input fields.
+ $('#l10n-client-string-editor .source-text').html('');
+ $('#gL10nClientSaveForm #l10n-edit-target').val('');
+ },
+ error: function(xmlhttp) {
+ // TODO: Localize this message
+ alert('An HTTP error @status occured (or empty response).'.replace('@status', xmlhttp.status));
+ }
+ });
+
+
+ // Copy source text to translation field on button click.
+ $('#gL10nClientSaveForm #l10n-edit-copy').click(function() {
+ $('#gL10nClientSaveForm #l10n-edit-target').val($('#l10n-client-string-editor .source-text').text());
+ });
+
+ // Clear translation field on button click.
+ $('#gL10nClientSaveForm #l10n-edit-clear').click(function() {
+ $('#gL10nClientSaveForm #l10n-edit-target').val('');
+ });
+};
diff --git a/core/libraries/I18n.php b/core/libraries/I18n.php
index 1419357e..51bc2a51 100644
--- a/core/libraries/I18n.php
+++ b/core/libraries/I18n.php
@@ -48,11 +48,14 @@ function t2($singular, $plural, $count, $options=array()) {
}
class I18n_Core {
+ private static $_instance;
+
private $_config = array();
+ private $_call_log = array();
+
private $_cache = array();
- private static $_instance;
private function __construct($config) {
$this->_config = $config;
@@ -81,10 +84,11 @@ class I18n_Core {
$count = empty($options['count']) ? null : $options['count'];
$values = $options;
unset($values['locale']);
+ $this->log($message, $options);
$entry = $this->lookup($locale, $message);
- if (empty($entry)) {
+ if (null === $entry) {
// Default to the root locale.
$entry = $message;
$locale = $this->_config['root_locale'];
@@ -111,9 +115,7 @@ class I18n_Core {
}
}
- // If message is an array (plural forms), use the first form as message id.
- $key = is_array($message) ? array_shift($message) : $message;
- $key = md5($key, true);
+ $key = self::getMessageKey($message);
if (isset($this->_cache[$locale][$key])) {
return $this->_cache[$locale][$key];
@@ -122,6 +124,33 @@ class I18n_Core {
}
}
+ public function hasTranslation($message, $options=null) {
+ $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale'];
+ $count = empty($options['count']) ? null : $options['count'];
+ $values = $options;
+ unset($values['locale']);
+ $this->log($message, $options);
+
+ $entry = $this->lookup($locale, $message);
+
+ if (null === $entry) {
+ return false;
+ } else if (!is_array($entry)) {
+ return $entry !== '';
+ } else {
+ $plural_key = self::get_plural_key($locale, $count);
+ return isset($entry[$plural_key])
+ && $entry[$plural_key] !== null
+ && $entry[$plural_key] !== '';
+ }
+ }
+
+ public static function getMessageKey($message) {
+ // If message is an array (plural forms), use the first form as message id.
+ $key = is_array($message) ? array_shift($message) : $message;
+ return md5($key, true);
+ }
+
private function interpolate($locale, $string, $values) {
// TODO: Handle locale specific number formatting.
@@ -157,6 +186,15 @@ class I18n_Core {
}
}
+ private function log($message, $options) {
+ $key = self::getMessageKey($message);
+ isset($this->_call_log[$key]) or $this->_call_log[$key] = array($message, $options);
+ }
+
+ public function getCallLog() {
+ return $this->_call_log;
+ }
+
private static function get_plural_key($locale, $count) {
$parts = explode('_', $locale);
$language = $parts[0];
diff --git a/core/tests/I18n_Test.php b/core/tests/I18n_Test.php
index 20f65528..df139f04 100644
--- a/core/tests/I18n_Test.php
+++ b/core/tests/I18n_Test.php
@@ -32,7 +32,7 @@ class I18n_Test extends Unit_Test_Case {
->where("locale", "te_ST")
->delete_all();
- $messages_de_DE = array(
+ $messages_te_ST = array(
array('Hello world', 'Hallo Welt'),
array(array('one' => 'One item has been added',
'other' => '%count elements have been added'),
@@ -40,12 +40,11 @@ class I18n_Test extends Unit_Test_Case {
'other' => '%count Elemente wurden hinzugefuegt.')),
array('Hello %name, how are you today?', 'Hallo %name, wie geht es Dir heute?'));
- foreach ($messages_de_DE as $data) {
+ foreach ($messages_te_ST as $data) {
list ($message, $translation) = $data;
$key = $message;
- $key = is_array($key) ? array_shift($key) : $key;
$entry = ORM::factory("incoming_translation");
- $entry->key = md5($key, true);
+ $entry->key = I18n::getMessageKey($message);
$entry->message = serialize($message);
$entry->translation = serialize($translation);
$entry->locale = 'te_ST';
diff --git a/core/views/l10n_client.html.php b/core/views/l10n_client.html.php
new file mode 100644
index 00000000..7c09df7b
--- /dev/null
+++ b/core/views/l10n_client.html.php
@@ -0,0 +1,30 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+
+<div id='l10n-client' class='hidden'>
+ <div class='labels'>
+ <span class='toggle'><?= t('Translate Text') ?></span>
+ <div class='label strings'><h2><?= t('Page Text') ?></h2></div>
+ <div class='label source'><h2><?= t('Source') ?></div>
+ <div class='label translation'><h2><?= t('Translation to %language',
+ array('%language' => 'TODO')) ?></h2></div>
+ </div>
+ <div id='l10n-client-string-select'>
+ <ul class='string-list'>
+ <? foreach ($string_list as $string): ?>
+ <li class='<?= $string["translation"] === '' ? "untranslated" : "translated" ?>'><?= $string["source"] ?></li>
+ <? endforeach; ?>
+ </ul>
+ <?= $l10n_search_form ?>
+ </div>
+ <div id='l10n-client-string-editor'>
+ <div class='source'>
+ <div class='source-text'></div>
+ </div>
+ <div class='translation'>
+ <?= $l10n_form ?>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var l10n_client_data = <?= json_encode($string_list) ?>;
+ </script>
+</div>
diff --git a/core/views/scaffold.html.php b/core/views/scaffold.html.php
index 2dd256c2..b54e6cd2 100644
--- a/core/views/scaffold.html.php
+++ b/core/views/scaffold.html.php
@@ -440,6 +440,13 @@
<?= html::anchor("scaffold/session/debug?value=1", "on") ?> <b>off</b>
<? endif ?>
</li>
+ <li> Translation Mode:
+ <? if (Session::instance()->get("l10n_mode", false)): ?>
+ <b>on</b> <?= html::anchor("scaffold/session/l10n_mode?value=0", "off") ?>
+ <? else: ?>
+ <?= html::anchor("scaffold/session/l10n_mode?value=1", "on") ?> <b>off</b>
+ <? endif ?>
+ </li>
</ul>
</div>