diff --git l10n_community/ajax.inc l10n_community/ajax.inc
deleted file mode 100644
index 779ea10..0000000
--- l10n_community/ajax.inc
+++ /dev/null
@@ -1,166 +0,0 @@
-project_title != $previous_project) {
- if (!empty($list)) {
- $output[] = join(', ', $list);
- }
- $list = array(''. $instance->project_title .': '. $instance->release_title .' ('. $instance->occurance_count .')');
- }
- else {
- $list[] = $instance->release_title .' ('. $instance->occurance_count .')';
- }
- $previous_project = $instance->project_title;
- }
- $output[] = join(', ', $list);
- $usage_info = ''. t('Used in:') .''. theme('item_list', $output);
-
- // Information about translator and translation timestamp.
- $translation_info = '';
- $translation = db_fetch_object(db_query("SELECT translation, uid_entered, time_entered FROM {l10n_community_translation} WHERE language = '%s' AND sid = %d AND is_active = 1 AND is_suggestion = 0", $langcode, $sid));
- if (!empty($translation->translation)) {
- $account = user_load(array('uid' => $translation->uid_entered));
- $translation_info = t('Translated by:
%username at %date', array('%username' => $account->name, '%date' => format_date($translation->time_entered)));
- }
-
- print $usage_info . $translation_info; // . $suggestion_info;
- exit;
-}
-
-function l10n_community_string_suggestions($langcode = NULL, $sid = 0) {
- global $user;
-
- // Existing, "unresolved" suggestions.
- $suggestions = array();
- $result = db_query("SELECT t.tid, t.sid, t.translation, t.uid_entered, t.time_entered, u.name FROM {l10n_community_translation} t LEFT JOIN {users} u ON u.uid = t.uid_entered WHERE t.language = '%s' AND t.sid = %d AND t.is_active = 1 AND t.is_suggestion = 1 ORDER BY t.time_entered", $langcode, $sid);
-
- $perm = l10n_community_get_permission($langcode);
- while ($suggestion = db_fetch_object($result)) {
- // This detail pane is only retrieved from JS, so we are free to output obtrusive JS here.
- $token = "'". drupal_get_token('l10n_server_'. $suggestion->tid .'_'. $suggestion->sid) ."'";
- $approve_button = $decline_button = '';
- if ($perm & ($suggestion->uid_entered == $user->uid ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS)) {
- $approve_button = theme('l10n_community_button', 'approve', 'l10n-approve', 'onclick="l10nCommunity.approveSuggestion('. $suggestion->tid .','. $suggestion->sid .', this, '. $token .');" title="'. t('Approve suggestion.') .'"');
- $decline_button = theme('l10n_community_button', 'decline', 'l10n-decline', 'onclick="l10nCommunity.declineSuggestion('. $suggestion->tid .','. $suggestion->sid .', this, '. $token .');" title="'. t('Decline suggestion.') .'"');
- }
-
- // Plural versions are shown in a short form.
- // Since we no longer store an additional copy of strings in JS
- // and the null byte is stripped out by browsers (?), we need to
- // use a DOM-safe delimiter: "; "
- $translation = strpos($suggestion->translation, "\0") ? str_replace(chr(0), "; ", $suggestion->translation) : $suggestion->translation;
- $translation = l10n_community_format_text($translation, $suggestion->sid);
- $suggestions[] = $translation ."
tags.
+ string = string.replace(/(<.+?(>|$))/g, function (str) {
+ return '' + str.replace(/<[^>]+>/g, '$&') + '';
+ });
+
+ string = string.replace(/\\[^<]/g, '$&');
+ string = string.replace(/\n/g, '$&');
+ return string;
+ };
+
+ $('td.translation').parent().each(function () {
+ var all = $('li.translation', this);
+ var strings = all.find('.l10n-string > span');
+ var source = $('td.source', this);
+
+ source.find('.l10n-string span').each(function () {
+ $(this).html(markup($(this).html()));
+ });
+
+ strings.each(function () {
+ var orig = $(this).html(), markedUp = markup(orig);
+ $(this)
+ .html(markedUp)
+ .data('worddiff:original', orig)
+ .data('worddiff:markup', markedUp);
+ });
+
+ var setStatus = function (elem, status, value) {
+ newValue = elem.find('.' + status + ' :checkbox').attr('checked', value).attr('checked');
+ elem[(newValue === undefined ? value : newValue) ? 'addClass' : 'removeClass']('is-' + status);
+ };
+
+ var textareas = all.filter('.new-translation').find('textarea');
+
+ $(this).find('ul.actions .edit').click(function () {
+ var translation = $(this).closest('td.source, li.translation');
+ var confirmed = undefined;
+ textareas.each(function (i) {
+ var textarea = $(this);
+ var val = textarea.val();
+ if (confirmed || val === textarea.attr('defaultValue') || !val || (confirmed === undefined && (confirmed = confirm("Do you want to overwrite the current suggestion?")))) {
+ textarea.val(translation.find('.l10n-string > span:eq('+ i +')').text()).keyup();
+ if (i == 0) {
+ // Since we can't have multiple focuses, we jut focus the first textarea.
+ textarea.focus();
+ }
+ }
+ });
+ });
+
+ all.each(function () {
+ var translation = $(this);
+ var isTranslation = !translation.is('.no-translation');
+ var siblings = all.not(this).not('.no-translation');
+
+ var removeDiff = function () {
+ strings.worddiffRevert();
+ };
+
+ var updateDiff = function () {
+ removeDiff();
+ if (isTranslation) {
+ var orig = siblings.filter('.is-active');
+ if (!orig.length)
+ orig = siblings.filter('.default');
+ if (!orig.length)
+ orig = all.not('.no-translation').eq(0).not(translation);
+ if (orig.length) {
+ orig = orig.find('.l10n-string > span');
+ translation.find('.l10n-string > span').each(function (i) {
+ $(this).worddiff(orig.get(i), markup);
+ });
+ }
+ }
+ };
+
+ translation.find('> .selector').click(function () {
+ setStatus(translation, 'declined', false);
+ // Mark this as the active translation.
+ setStatus(translation.siblings('.is-active:not(.new-translation)'), 'declined', true);
+ setStatus(translation.siblings('.is-active'), 'active', false);
+ translation.addClass('is-active');
+ });
+
+ translation.find('> .actions .declined :checkbox').change(function () {
+ setStatus(translation, 'declined', this.checked);
+ });
+
+ translation.find('> .actions .stable :checkbox').change(function () {
+ setStatus(translation, 'stable', this.checked);
+ });
+
+ translation.find('> .author span[title]').click(function () {
+ var $this = $(this), html = $this.html();
+ $this.html($this.attr('title')).attr('title', html);
+ });
+
+ if (isTranslation) {
+ translation.find('.l10n-string').dblclick(function () {
+ translation.siblings().not('.new-translation').each(function () {
+ setStatus($(this), 'declined', true);
+ });
+ });
+
+ translation
+ .mouseenter(updateDiff)
+ .mouseleave(removeDiff);
+ }
+
+ if (translation.is('.new-translation')) {
+ translation.find('> .selector').click(function () {
+ textareas.each(function () {
+ var textarea = $(this);
+ if (textarea.val() === '' || textarea.val() === textarea.attr('defaultValue')) {
+ textarea.focus();
+ // Stop checking the other ones.
+ return false;
+ }
+ });
+ });
+
+ var hasContent = function () {
+ for (var i = 0; i < textareas.length; i++) {
+ if (textareas[i].value && textareas[i].value !== textareas[i].defaultValue) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ var blurTimeout;
+ textareas.each(function (n) {
+ var wrapper = $(this);
+ var textarea = $(this);
+ var text = translation.find('.l10n-string > span').eq(n);
+
+ textarea
+ .focus(function () {
+ translation.addClass('focussed');
+ clearTimeout(blurTimeout);
+ if (textarea.val() === textarea.attr('defaultValue')) {
+ textarea.val('');
+ }
+ })
+ .blur(function () {
+ blurTimeout = setTimeout(function () {
+ translation.removeClass('focussed');
+ if (textarea.val() === '') {
+ textarea.val(textarea.attr('defaultValue'));
+ }
+ translation[hasContent() ? 'addClass' : 'removeClass']('has-content');
+ }, 1000);
+ })
+ .keyup(function () {
+ var val = encode(textarea.val());
+ text
+ .data('worddiff:original', val)
+ .data('worddiff:markup', markup(val));
+ var oldPos = textarea.offset().top;
+ updateDiff();
+ var diff = textarea.offset().top - oldPos;
+ if (diff)
+ window.scrollBy(0, diff);
+ });
+ });
+ }
+ });
+ });
+ });
+})(jQuery);
diff --git l10n_community/extractor.inc l10n_community/extractor.inc
index 8f00f1f..06e16fd 100644
--- l10n_community/extractor.inc
+++ l10n_community/extractor.inc
@@ -200,7 +200,7 @@ function l10n_community_save_string($value = NULL, $context = NULL, $file = NULL
if (isset($files[$file])) {
// Explode files array to pid, rid and fid.
list($pid, $rid, $fid) = $files[$file];
-
+
// A \0 separator in the string means we deal with a string with plural variants.
// Unlike Drupal core, we store all in the same string, as it is easier
// to handle later, and we don't need the individual string parts.
diff --git l10n_community/images/decline.png l10n_community/images/decline.png
new file mode 100755
index 0000000..c8a5058
Binary files /dev/null and l10n_community/images/decline.png differ
diff --git l10n_community/images/edit.png l10n_community/images/edit.png
new file mode 100755
index 0000000..37aced3
Binary files /dev/null and l10n_community/images/edit.png differ
diff --git l10n_community/images/icon_approval.gif l10n_community/images/icon_approval.gif
deleted file mode 100644
index b4290ba..0000000
Binary files l10n_community/images/icon_approval.gif and /dev/null differ
diff --git l10n_community/images/icon_copy.gif l10n_community/images/icon_copy.gif
deleted file mode 100644
index 4be8507..0000000
Binary files l10n_community/images/icon_copy.gif and /dev/null differ
diff --git l10n_community/images/icon_toolbox.gif l10n_community/images/icon_toolbox.gif
deleted file mode 100644
index 3c3f40e..0000000
Binary files l10n_community/images/icon_toolbox.gif and /dev/null differ
diff --git l10n_community/images/newline.png l10n_community/images/newline.png
deleted file mode 100644
index 5b7c3dc..0000000
Binary files l10n_community/images/newline.png and /dev/null differ
diff --git l10n_community/images/stable-active.png l10n_community/images/stable-active.png
new file mode 100755
index 0000000..53542e7
Binary files /dev/null and l10n_community/images/stable-active.png differ
diff --git l10n_community/images/stable-inactive.png l10n_community/images/stable-inactive.png
new file mode 100755
index 0000000..262915a
Binary files /dev/null and l10n_community/images/stable-inactive.png differ
diff --git l10n_community/images/undecline.png l10n_community/images/undecline.png
new file mode 100755
index 0000000..35ad7c2
Binary files /dev/null and l10n_community/images/undecline.png differ
diff --git l10n_community/import.inc l10n_community/import.inc
index cd86e98..041cfcf 100644
--- l10n_community/import.inc
+++ l10n_community/import.inc
@@ -129,10 +129,8 @@ function l10n_community_import_form_submit($form, &$form_state) {
// Do the actual parsing on the local file.
if (l10n_community_import($file, $form_state['values']['langcode'], $form_state['values']['is_suggestion'], $form_state['values']['import_uid'])) {
- // Get status report on what was done in the process.
- list($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored) = _l10n_community_import_one_string();
drupal_set_message(t('The translation was successfully imported.'));
- l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored);
+ l10n_community_update_message();
cache_clear_all('l10n:stats:'. $form_state['values']['langcode'], 'cache');
}
}
@@ -331,20 +329,10 @@ function l10n_community_import($file, $langcode, $is_suggestion, $uid) {
* @param $uid
* User id used to save attribution information.
*/
-function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $is_suggestion = FALSE, $uid = NULL) {
+function _l10n_community_import_one_string($value, $langcode = NULL, $is_suggestion = FALSE, $uid = NULL) {
global $user;
- static $inserted = 0;
- static $updated = 0;
- static $unchanged = 0;
- static $suggested = 0;
- static $duplicates = 0;
- static $ignored = 0;
-
- if ($value == NULL) {
- // Result stats queried.
- return array($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored);
- }
+ static $counters = array();
// Trim translation (we will apply source string based whitespace later).
if (is_string($value['msgstr'])) {
@@ -389,20 +377,20 @@ function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $is_
if ($is_suggestion || !$translation) {
if (!l10n_community_is_duplicate($value['msgstr'], $sid, $langcode)) {
- l10n_community_target_save($sid, $value['msgstr'], $langcode, $uid, $is_suggestion, $inserted, $updated, $unchanged, $suggested);
+ l10n_community_target_save($sid, $value['msgstr'], $langcode, $uid, $is_suggestion);
}
else {
- $duplicates++;
+ l10n_community_counter('duplicates');
}
}
else {
// We certainly did not update this one.
- $unchanged++;
+ l10n_community_counter('unchanged');
}
}
else {
// Source string not found, string ignored.
- $ignored++;
+ l10n_community_counter('ignored');
}
}
}
diff --git l10n_community/jquery.worddiff.js l10n_community/jquery.worddiff.js
new file mode 100644
index 0000000..3e2efb2
--- /dev/null
+++ l10n_community/jquery.worddiff.js
@@ -0,0 +1,183 @@
+(function($) {
+ var Fragment = function (string) {
+ this.content = string;
+ this.equiv = false;
+ };
+
+ Fragment.prototype.toString = function (tag) {
+ if (this.equiv || !tag)
+ return this.content;
+ else
+ return '<' + tag + '>' + this.content + '' + tag + '>';
+ };
+
+
+ var moveToEnd = function (a, i, k) {
+ if (!a.equiv && (!k[i-1] || k[i-1].equiv)) {
+ // Find next item equiv item.
+ for (var j = i+1; k[j] && !k[j].equiv; j++);
+ if (k[j] && k[j].content === a.content) {
+ k[i] = k[j];
+ k[j] = a;
+ }
+ }
+ };
+
+ var aggregate = function (a, i, k) {
+ if (!a.equiv && k[i+1] && !k[i+1].equiv) {
+ k[i+1].content = a.content + k[i+1].content;
+ delete k[i];
+ }
+ };
+
+ var join = function (what, t) {
+ return $.map(what, function (a) {
+ if (a) return a.toString(t);
+ }).join('');
+ };
+
+
+ $.wordDiff = {
+ nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/,
+
+ tokenize: function (args) {
+ // Split on non-word characters.
+ for (var type in args) {
+ args[type] = $.grep(
+ args[type].split($.wordDiff.nonWord),
+ function (s) { return s.length; }
+ );
+ }
+
+ // Calculate the indexes and offsets for common suffixes and prefixes.
+ var i = -1, j = args.del.length, k = args.ins.length;
+ while (args.del[++i] === args.ins[i] && i <= j);
+ while (j >= i && k >= i && args.del[--j] === args.ins[--k]);
+
+ args.prefix = args.del.slice(0, i).join('');
+ args.suffix = args.del.slice(j + 1).join('');
+ args.del = args.del.slice(i, ++j);
+ args.ins = args.ins.slice(i, ++k);
+ },
+
+ lcs: function (args) {
+ var matrix = [];
+
+ for (var i = 0; i < args.del.length; i++) {
+ matrix[i] = [];
+ for (var j = 0; j < args.ins.length; j++) {
+ if (args.del[i] === args.ins[j]) {
+ matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length;
+ }
+ else {
+ matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0);
+ }
+ }
+ }
+
+ return matrix;
+ },
+
+ changeset: function (args, matrix) {
+ var result = {};
+
+ $.each(['del', 'ins'], function () {
+ result[this] = $.map(args[this], function (a) { return new Fragment(a); });
+ });
+
+ // Backtrack through the matrix.
+ for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) {
+ if (j < 0 || result.del[i].content !== result.ins[j].content) {
+ if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) {
+ j++;
+ }
+ else {
+ i++;
+ }
+ }
+ else {
+ result.del[i] = result.ins[j];
+ result.del[i].equiv = true;
+ }
+ }
+
+ // Fill up gaps.
+ for (var i = 0; i < result.del.length; i++) {
+ if (result.del[i].equiv && result.del[i].content.length < 3) {
+ var j = result.ins.indexOf(result.del[i]);
+ if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){
+ result.del[i].equiv = false;
+ result.ins[j] = $.extend(true, {}, result.del[i]);
+ }
+ }
+ }
+
+ $.each(['del', 'ins'], function () {
+ // Try to move changes to the end.
+ for (var i = 0; i < result[this].length; i++)
+ moveToEnd(result[this][i], i, result[this]);
+
+ // Aggregate subsequent changes to minimize ins/del tags.
+ for (var i = 0; i < result[this].length; i++)
+ aggregate(result[this][i], i, result[this]);
+ });
+
+ return result;
+ },
+
+ render: function (args, result) {
+ var diff = {
+ del: args.prefix + join(result.del, 'del') + args.suffix,
+ ins: args.prefix + join(result.ins, 'ins') + args.suffix
+ };
+
+ return diff;
+ },
+
+ diff: function (del, ins) {
+ var args = { 'del': del, 'ins': ins };
+
+ $.wordDiff.tokenize(args);
+ var matrix = $.wordDiff.lcs(args);
+ var result = $.wordDiff.changeset(args, matrix);
+ return $.wordDiff.render(args, result);
+ }
+ };
+
+ $.fn.worddiff = function (selector, processor) {
+ var src = $(selector), del = src.data('worddiff:original');
+ if (del === undefined)
+ src.data('worddiff:original', del = src.html());
+
+ return this.each(function () {
+ var dst = $(this), ins = dst.data('worddiff:original');
+ if (ins === undefined)
+ dst.data('worddiff:original', ins = dst.html());
+
+ var diff = $.wordDiff.diff(del, ins);
+ if (processor) {
+ diff.del = processor(diff.del);
+ diff.ins = processor(diff.ins);
+ }
+
+ if (diff.del && diff.ins) {
+ src.html(diff.del).addClass('worddiff-del');
+ dst.html(diff.ins).addClass('worddiff-ins');
+ }
+ });
+ };
+
+ $.fn.worddiffRevert = function () {
+ return this.each(function () {
+ var dest = $(this), orig = dest.data('worddiff:markup');
+ if (orig === undefined) {
+ orig = dest.data('worddiff:original');
+ }
+ if (orig !== undefined) {
+ dest
+ .html(orig)
+ .removeClass('worddiff-del').removeClass('worddiff-ins');
+ }
+ });
+ };
+})(jQuery);
\ No newline at end of file
diff --git l10n_community/l10n_community.css l10n_community/l10n_community.css
index 1db630a..98fd2eb 100644
--- l10n_community/l10n_community.css
+++ l10n_community/l10n_community.css
@@ -62,147 +62,3 @@ table.l10n-server-filter td.last {
em.l10n-community-marker {
font-weight: bold;
}
-
-/* Translation editor styles */
-#l10n-community-translate-view .pager,
-#l10n-community-translate-form .pager {
- margin:1em 0em;
-}
-
-table.l10n-server-translate .hidden {
- display:none;
-}
-
-table.l10n-server-translate input.form-text,
-table.l10n-server-translate textarea {
- width:95%;
-}
-
-table.l10n-server-translate td {
- vertical-align:top;
- width:50%;
-}
-
- table.l10n-server-translate td.source {
- padding:10px 5px;
- }
-
- table .string-context {
- color: #777;
- font-size: 0.8em;
- }
-
- table.l10n-server-translate td.translation {
- padding:0px;
- }
-
- table.l10n-server-translate td .l10n-panes {
- min-height:60px;
- position:relative;
- padding:0px 20px 0px 0px;
- }
-
- table.l10n-server-translate td .pane {
- padding:10px 5px;
- }
-
- table.l10n-server-translate td .suggestions,
- table.l10n-server-translate td .lookup {
- display:none;
- }
-
-table.l10n-server-translate .toolbox {
- position:absolute; top:0px; right:0px;
-}
-
-table.l10n-server-translate tr .form-item {
- white-space: normal;
-}
-
-table.l10n-server-translate label.option {
- font-size:90%;
-}
-
-.l10n-button {
- cursor:pointer;
- display: block;
- width: 20px;
- height: 20px;
- overflow: hidden;
- text-indent: -9999px;
-}
-
- .l10n-approval-buttons {
- padding-right:10px;
- float:right;
- }
-
- .l10n-approval-buttons .l10n-button {
- float:left;
- }
-
- .l10n-approval-buttons .l10n-approve,
- .l10n-save {
- background: url(images/icon_approval.gif) 0px 0px no-repeat;
- }
-
- .l10n-approval-buttons .l10n-approve:active,
- .l10n-save:active {
- background-position: 0px bottom;
- }
-
- .l10n-approval-buttons .l10n-decline,
- .l10n-clear {
- width:21px;
- background: url(images/icon_approval.gif) -20px 0px no-repeat;
- }
-
- .l10n-approval-buttons .l10n-decline:active,
- .l10n-clear:active {
- background-position: -20px bottom;
- }
-
- .l10n-community-copy {
- background: url(images/icon_copy.gif) no-repeat;
- }
-
- .l10n-translate {
- background: url(images/icon_toolbox.gif) 0px 0px no-repeat;
- }
-
- .l10n-translate.active {
- background: url(images/icon_toolbox.gif) right 0px no-repeat;
- }
-
- .l10n-suggestions {
- background: url(images/icon_toolbox.gif) 0px -40px no-repeat;
- }
-
- .l10n-suggestions.active {
- background: url(images/icon_toolbox.gif) right -40px no-repeat;
- }
-
- .l10n-lookup {
- background: url(images/icon_toolbox.gif) 0px -20px no-repeat;
- }
-
- .l10n-lookup.active {
- background: url(images/icon_toolbox.gif) right -20px no-repeat;
- }
-
-ul.l10n-community-strings {
- margin: 0;
-}
-
-ul.l10n-community-strings li {
- padding-left: 20px;
-}
-
-ul.l10n-community-strings li .buttons {
- margin-left:-20px;
- float:left;
-}
-
-.l10n-community-string .original {
- display:none;
-}
diff --git l10n_community/l10n_community.info l10n_community/l10n_community.info
index 07c0ddd..977e6df 100644
--- l10n_community/l10n_community.info
+++ l10n_community/l10n_community.info
@@ -2,6 +2,7 @@
name = "Localization community"
description = A community interface for string translation
dependencies[] = locale
+dependencies[] = jquery_update
package = "Localization server"
core = 6.x
php = 5
diff --git l10n_community/l10n_community.js l10n_community/l10n_community.js
deleted file mode 100644
index e96bf79..0000000
--- l10n_community/l10n_community.js
+++ /dev/null
@@ -1,220 +0,0 @@
-// $Id: l10n_community.js,v 1.1.2.8.2.5 2009-10-15 08:46:40 goba Exp $
-
-l10nCommunity = {};
-
-l10nCommunity.switchPanes = function(elem, id) {
- if ($(elem).parents('.translation').find('.'+id).css('display') == 'none') {
- // execute animation once per translation
- var once = 0;
- $(elem).parents('.translation').find('.pane:not(.'+id+')').each(function() {
- if ($(this).css('display') == 'block') {
- $(this).slideUp('fast', function() {
- if (once == 0) {
- once = 1;
- $(this).parents('.translation').find('.'+id).slideDown('fast');
- }
- });
- }
- });
- }
- $(elem).parents('.toolbox').find('span.l10n-button').removeClass('active');
- $(elem).addClass('active');
-}
-
-/**
- * Initialize action images: toolboxes and string copy buttons.
- */
-l10nCommunity.init = function() {
- // Only attempt to register events if form exists
- if ($('#l10n-community-translate-form').size() > 0) {
- // When the copy button is clicked, copy the original string value to the
- // translation field for the given strings. Relations are maintained with
- // the string ideitifiers.
- $('span.l10n-community-copy').click(function() {
- l10nCommunity.copyString(this);
- ;})
-
- // Screw AJAX submit -- we'll just hard submit the form for now
- $('span.l10n-save').click(function() {
- $('#l10n-community-translate-form').submit();
- });
-
- // Clear sibling text fields
- $('span.l10n-clear').click(function() {
- $(this).parents('.translation').find('input.form-text, textarea').val('');
- });
-
-
- $('#l10n-community-translate-form .l10n-translate').click(function() {
- // switch display panes
- l10nCommunity.switchPanes(this, 'translate');
- });
-
- $('#l10n-community-translate-form .l10n-lookup').click(function() {
- // switch display panes
- var elem = this;
- if ($(this).is(".active")) {
- // Switch back to editing form if already clicked. Convenience feature,
- // so that you don't need to move your mouse to switch back.
- var parent = $(elem).parents('.translation');
- var tool = $('.l10n-translate', parent);
- l10nCommunity.switchPanes(tool, 'translate');
- return;
- }
- var sid = $(this).parents('.translation').attr('id').substring(6);
- $.get(Drupal.settings.l10n_details_callback + sid, null, function(data) {
- $('#tpane-' + sid + ' .lookup').empty().append(data);
- l10nCommunity.switchPanes(elem, 'lookup');
- });
- });
-
- $('#l10n-community-translate-form .l10n-suggestions').click(function() {
- // switch display panes
- var elem = this;
- if ($(this).is(".active")) {
- // Switch back to editing form if already clicked. Convenience feature,
- // so that you don't need to move your mouse to switch back.
- var parent = $(elem).parents('.translation');
- var tool = $('.l10n-translate', parent);
- l10nCommunity.switchPanes(tool, 'translate');
- return;
- }
- var sid = $(this).parents('.translation').attr('id').substring(6);
- $.get(Drupal.settings.l10n_suggestions_callback + sid, null, function(data) {
- $('#tpane-' + sid + ' .suggestions').empty().append(data);
- l10nCommunity.switchPanes(elem, 'suggestions');
- var suggestions = $('#tpane-' + sid + ' .suggestions');
- $('span.l10n-community-copy', suggestions).click(function() {
- l10nCommunity.copyString(this);
- });
- // Hide the has-suggestion marker if we don't have suggestions anymore.
- // Could happen if we declined the last suggestion and reloading.
- $('#l10n-community-editor-' + sid + ' .l10n-no-suggestions').parent().parent().find('.l10n-has-suggestion').hide();
- });
- });
- }
-}
-
-l10nCommunity.copyString = function(elem) {
- var item = $(elem).parents('li').find('div.string > div');
- var parentlist = $(item).parents('ul.l10n-community-strings');
- var original = $('.original', item).text();
- var sid = item.attr('class').substring(7);
-
- if (sid.indexOf('-') > 0) {
- // Copying orignal plural string or active plural translation. We should
- // get the original sid prefix and then copy the strings to the textareas.
- sid = sid.split('-');
- sid = sid[0];
- for (i = 0; i < Drupal.settings.l10n_num_plurals; i++) {
- source_index = (i > 0 ? 1 : 0);
- $('#l10n-community-translation-'+ sid +'-'+ i).val($('.string-'+ sid +'-'+ source_index +' .original', parentlist).text());
- }
- }
- else if (original.indexOf("; ") > 0) {
- // Copying plural suggestions from the suggestion list. This has the special
- // "; " delimiter (which we suppose does not exist in source strings).
- var strings = original.split("; ");
- for (string in strings) {
- $('#l10n-community-translation-'+ sid +'-'+ string).val(strings[string]);
- }
- }
- else {
- // Otherwise simple string. Just copy over the original string.
- $('#l10n-community-translation-' + sid).val(original);
- }
-
- // Show the editing controls.
- $('#l10n-community-wrapper-' + sid).show();
-
- // Switch to translate pane. The pane is in the translation column, so
- // if we are in the source column, we need to switch columns.
- var parent = $(elem).parents('td.source, td.translation');
- if (parent.get(0).className == 'source') {
- parent = $(parent.get(0)).siblings();
- }
- var tool = $('.l10n-translate', parent);
- l10nCommunity.switchPanes(tool, 'translate');
-}
-
-/**
- * Suggestion approval callback.
- */
-l10nCommunity.approveSuggestion = function(tid, sid, elem, token) {
- // Invoke server side callback to save the approval.
- $.ajax({
- type: "GET",
- url: Drupal.settings.l10n_approve_callback + tid + Drupal.settings.l10n_form_token_path + token,
- success: function (data) {
- if (data == 'done') {
- // Empty translate pane and inform user that the suggestion was saved.
- $('#tpane-'+ sid +' .translate').empty().append(Drupal.settings.l10n_approve_confirm);
- // Switch back to translate pane and hide suggestion icon
- var parent = $(elem).parents('.translation');
- l10nCommunity.switchPanes($('.l10n-translate', parent), 'translate');
- $('.l10n-suggestions', parent).hide();
- }
- else {
- alert(Drupal.settings.l10n_approve_error);
- };
- },
- error: function (xmlhttp) {
- // Being an internal/system error, this is not translatable.
- alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ uri);
- }
- });
- // Return false for onclick handling.
- return false;
-}
-
-/**
- * Suggestion decline callback.
- */
-l10nCommunity.declineSuggestion = function(tid, sid, elem, token) {
- // Invoke server side callback to save the decline action.
- $.ajax({
- type: "GET",
- url: Drupal.settings.l10n_decline_callback + tid + Drupal.settings.l10n_form_token_path + token,
- success: function (data) {
- if (data == 'done') {
- // Reload info pane.
- $('#tpane-' + sid + ' .l10n-suggestions').click();
- }
- else {
- alert(Drupal.settings.l10n_decline_error);
- };
- },
- error: function (xmlhttp) {
- // Being an internal/system error, this is not translatable.
- alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ uri);
- }
- });
- // Return false for onclick handling.
- return false;
-}
-
-/**
- * Suggestion editing copy callback.
- */
-l10nCommunity.copySuggestion = function(sid, translation) {
- if (translation.indexOf("\O") > 0) {
- // If we have the null byte, suggestion has plurals, so we need to
- // copy over the distinct strings to the distinct textareas.
- var strings = translation.split("\O");
- for (string in strings) {
- $('#l10n-community-translation-'+ sid +'-'+ string).val(strings[string]);
- }
- }
- else {
- // Otherwise standard string.
- $('#l10n-community-translation-'+ sid).val(translation);
- }
- // Show the editing controls.
- $('#l10n-community-wrapper-'+ sid).css('display', 'block');
- return false;
-}
-
-// Global killswitch
-if (Drupal.jsEnabled) {
- $(document).ready(l10nCommunity.init);
-}
diff --git l10n_community/l10n_community.module l10n_community/l10n_community.module
index bbd3de7..3d32f95 100644
--- l10n_community/l10n_community.module
+++ l10n_community/l10n_community.module
@@ -207,33 +207,10 @@ function l10n_community_menu() {
$items['translate/details'] = array(
'title' => 'String details',
'page callback' => 'l10n_community_string_details',
- 'file' => 'ajax.inc',
- 'access arguments' => array('browse translations'),
- 'type' => MENU_CALLBACK,
- );
- $items['translate/suggestions'] = array(
- 'title' => 'String suggestions',
- 'page callback' => 'l10n_community_string_suggestions',
- 'file' => 'ajax.inc',
+ 'file' => 'translate.inc',
'access arguments' => array('browse translations'),
'type' => MENU_CALLBACK,
);
- $items['translate/approve'] = array(
- 'title' => 'Approve suggestion',
- 'page callback' => 'l10n_community_string_approve',
- 'file' => 'ajax.inc',
- // Permission is enforced in l10n_community_string_ajax_suggestion().
- 'access arguments' => array('access localization community'),
- 'type' => MENU_CALLBACK,
- );
- $items['translate/decline'] = array(
- 'title' => 'Decline suggestion',
- 'page callback' => 'l10n_community_string_decline',
- 'file' => 'ajax.inc',
- // Permission is enforced in l10n_community_string_ajax_suggestion().
- 'access arguments' => array('access localization community'),
- 'type' => MENU_CALLBACK,
- );
// As soon as we have a language code, we can translate.
$items['translate/languages/%l10n_community_language'] = array(
@@ -251,8 +228,8 @@ function l10n_community_menu() {
'weight' => -20,
);
// Tabs to translate, import and export projects.
- $items['translate/languages/%l10n_community_language/view'] = array(
- 'title' => 'Browse',
+ $items['translate/languages/%l10n_community_language/translate'] = array(
+ 'title' => 'Translate',
'page callback' => 'l10n_community_translate_page',
'page arguments' => array(2),
'file' => 'translate.inc',
@@ -261,16 +238,6 @@ function l10n_community_menu() {
'type' => MENU_LOCAL_TASK,
'weight' => -10,
);
- $items['translate/languages/%l10n_community_language/edit'] = array(
- 'title' => 'Translate',
- 'page callback' => 'l10n_community_translate_page',
- 'page arguments' => array(2, 'edit'),
- 'file' => 'translate.inc',
- 'access callback' => 'l10n_community_has_permission',
- 'access arguments' => array(2, (string)L10N_PERM_SUGGEST),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => -8,
- );
$items['translate/languages/%l10n_community_language/moderate'] = array(
'title' => 'Moderate',
'page callback' => 'l10n_community_moderate_page',
@@ -496,6 +463,7 @@ function l10n_community_block_help() {
// We are dealing with a groups based permission model.
$permission_help[] = l10n_groups_block_help($perm, isset($args['langcode']) ? $args['langcode'] : NULL);
}
+
if ($perm & L10N_PERM_SUGGEST) {
$permission_notes[] = t('You can suggest translations to be reviewed by moderators of the team.');
}
@@ -949,6 +917,19 @@ function l10n_community_get_languages($key = NULL) {
}
/**
+ * Returns a language object for a specific language.
+ *
+ * @param $langcode
+ * Language code, for example 'hu', 'pt-br', 'de' or 'it'.
+ * @return
+ * A populated language object.
+ */
+function l10n_community_get_language($langcode) {
+ $languages = l10n_community_get_languages();
+ return $languages[$langcode];
+}
+
+/**
* Get translation permission level for a specific user.
*
* @param $langcode
@@ -958,7 +939,7 @@ function l10n_community_get_languages($key = NULL) {
* @return
* A bitmask of all permission flags the user has.
*/
-function l10n_community_get_permission($langcode, $account = NULL) {
+function l10n_community_get_permission($langcode, $account = NULL, $check = NULL) {
static $permissions = array();
global $user;
@@ -1000,12 +981,47 @@ function l10n_community_get_permission($langcode, $account = NULL) {
* @param $langcode
* Language code, for example 'hu', 'pt-br', 'de' or 'it'.
* @param $permission
- * The permission to check for.
+ * The permission to check for. Note that if you pass multiple permission
+ * constants, this function will return TRUE if the user has at least one
+ * of them.
* @return
* TRUE if the user has at least one of the specified permissions.
*/
function l10n_community_has_permission($langcode, $permission) {
- return (bool)(l10n_community_get_permission($langcode, NULL) & $permission);
+ return (bool)(l10n_community_get_permission($langcode) & $permission);
+}
+
+/**
+ * Checks whether the current user may approve a translation.
+ *
+ * @param $translation
+ * The translation in question.
+ * @return
+ * TRUE if the user may approve the translation.
+ */
+function l10n_community_may_approve_translation($translation) {
+ global $user;
+ $required = $translation->uid_entered == $user->uid ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS;
+ return l10n_community_has_permission($translation->language, $required);
+}
+
+/**
+ * Checks whether the current user may decline a translation.
+ *
+ * @param $translation
+ * The translation in question.
+ * @return
+ * TRUE if the user may decline the translation.
+ */
+function l10n_community_may_decline_translation($translation) {
+ global $user;
+ if ($translation->uid_entered == $user->uid) {
+ // Everyone may decline their own translations.
+ return TRUE;
+ }
+ else {
+ return l10n_community_has_permission($translation->language, L10N_PERM_MODERATE_OTHERS);
+ }
}
/**
@@ -1157,31 +1173,30 @@ function l10n_community_get_contexts() {
*
* @see l10n_community_is_duplicate()
*/
-function l10n_community_target_save($sid, $translation, $langcode, $uid, $suggestion, &$inserted, &$updated, &$unchanged, &$suggested) {
+function l10n_community_target_save($sid, $translation, $langcode, $uid, $suggestion) {
// Look for an existing active translation, if any.
$existing_string = db_fetch_object(db_query("SELECT sid, tid, translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode));
if (!empty($existing_string->sid)) {
-
// We have an active translation.
if ($existing_string->translation != $translation) {
// And what we should save now is different.
if ($suggestion) {
// Saving a suggestion, so set flag on translation.
db_query("UPDATE {l10n_community_translation} SET has_suggestion = 1 WHERE tid = %d", $existing_string->tid);
- $suggested++;
+ l10n_community_counter('suggested');
}
else {
// Saving a different translation -> deactivate previous translations and suggestions.
db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid = %d AND language = '%s';", $sid, $langcode);
- $updated++;
+ l10n_community_counter('updated');
}
db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, uid_approved, time_approved, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $uid, time(), ($suggestion ? 0 : $uid), ($suggestion ? 0 : time()), $suggestion);
}
else {
// Same string as existing translation.
- $unchanged++;
+ l10n_community_counter('unchanged');
}
}
@@ -1192,12 +1207,12 @@ function l10n_community_target_save($sid, $translation, $langcode, $uid, $sugges
// suggestions. We track and exclude these by translation = '' later.
db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, has_suggestion, is_active) VALUES (%d, '', '%s', 0, %d, 1, 1)", $sid, $langcode, time());
db_query("INSERT INTO {l10n_community_translation} (sid, language, translation, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, 1, 1)", $sid, $langcode, $translation, $uid, time());
- $suggested++;
+ l10n_community_counter('suggested');
}
else {
// No active translation yet -> INSERT.
db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, uid_approved, time_entered, time_approved, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $uid, $uid, time(), time());
- $inserted++;
+ l10n_community_counter('added');
}
}
}
@@ -1222,34 +1237,68 @@ function l10n_community_trim($translation, $source) {
}
/**
+ * Stores counters for status messages when modifying translations.
+ *
+ * @param $field
+ * The field to increment. Can be one of declined, approved, inserted,
+ * suggested, duplicates', ignored or unchanged.
+ * If not specified, the counters are returned and reset afterwards.
+ * @param $increment
+ * (Optional) The increment for the counter. Defaults to 1.
+ */
+function l10n_community_counter($field = NULL, $increment = 1) {
+ static $counters = array();
+ if (isset($field)) {
+ if (!isset($counters[$field])) {
+ $counters[$field] = 0;
+ }
+ $counters[$field] += $increment;
+ }
+ else {
+ $return = $counters;
+ $counters = array();
+ return $return;
+ }
+}
+
+/**
* Set a message based on the number of translations changed.
*
* Used by both the save and import process.
*/
-function l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored) {
- // Inform user about changes made.
- $message = array();
- if ($inserted) {
- $message[] = format_plural($inserted, '1 new translation added', '@count new translations added');
- }
- if ($suggested) {
- $message[] = format_plural($suggested, '1 new suggestion added', '@count new suggestions added');
- }
- if ($updated) {
- $message[] = format_plural($updated, '1 translation updated', '@count translations updated');
- }
- if ($unchanged) {
- $message[] = format_plural($unchanged, '1 translation unchanged', '@count translations unchanged');
- }
- if ($duplicates) {
- $message[] = format_plural($duplicates, '1 duplicate translation not saved', '@count duplicate translations not saved');
- }
- if ($ignored) {
- $message[] = format_plural($ignored, '1 source string not found, its translation ignored', '@count source strings not found, their translations were ignored');
- }
- if (count($message)) {
- drupal_set_message(join('; ', $message) .'.');
- }
+function l10n_community_update_message() {
+ $counters = l10n_community_counter();
+ $messages = array();
+
+ if (!empty($counters['declined']))
+ $messages[] = format_plural($counters['declined'], '1 translation declined', '@count translations declined');
+
+ if (!empty($counters['suggestion_declined']))
+ $messages[] = format_plural($counters['suggestion_declined'], '1 suggestion declined', '@count suggestion declined');
+
+ if (!empty($counters['approved']))
+ $messages[] = format_plural($counters['approved'], '1 translation approved', '@count translations approved');
+
+ if (!empty($counters['added']))
+ $messages[] = format_plural($counters['added'], '1 translation added', '@count translations added');
+
+ if (!empty($counters['suggested']))
+ $message[] = format_plural($counters['suggested'], '1 new suggestion added', '@count new suggestions added');
+
+ if (!empty($counters['updated']))
+ $message[] = format_plural($counters['updated'], '1 translation updated', '@count translations updated');
+
+ if (!empty($counters['duplicates']))
+ $message[] = format_plural($counters['duplicates'], '1 duplicate translation not saved', '@count duplicate translations not saved');
+
+ if (!empty($counters['ignored']))
+ $message[] = format_plural($counters['ignored'], '1 source string not found; its translation was ignored', '@count source strings not found; their translations were ignored');
+
+ if (!empty($counters['unchanged']))
+ $message[] = format_plural($counters['unchanged'], '1 translation unchanged', '@count translations unchanged');
+
+ if ($messages)
+ drupal_set_message(implode(', ', $messages));
}
/**
@@ -1310,15 +1359,6 @@ function l10n_community_project_uri_by_title($title) {
return db_result(db_query("SELECT uri FROM {l10n_community_project} WHERE title = '%s'", $title));
}
-/**
- * Check whether $suggestion is duplicate for $sid in $langcode.
- */
-function l10n_community_is_duplicate($suggestion, $sid, $langcode) {
- // Use BINARY matching to avoid marking case-corrections as duplicate.
- // Matches everything active, regardless of being translations or suggestions.
- return (bool) db_result(db_query("SELECT s.sid FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.translation = BINARY '%s' AND t.is_active = 1 AND t.language = '%s' AND s.sid = %d", $suggestion, $langcode, $sid));
-}
-
// = Theme functions ===========================================================
/**
@@ -1326,16 +1366,6 @@ function l10n_community_is_duplicate($suggestion, $sid, $langcode) {
*/
function l10n_community_theme($existing, $type, $theme, $path) {
return array(
- // l10n_community.module
- 'l10n_community_button' => array(
- 'arguments' => array('type' => NULL, 'class' => NULL, 'extras' => ''),
- ),
- 'l10n_community_strings' => array(
- 'arguments' => array('items' => NULL, 'form' => TRUE),
- ),
- 'l10n_community_copy_button' => array(
- 'arguments' => array(),
- ),
// pages.inc
'l10n_community_progress_columns' => array(
'arguments' => array('sum' => NULL, 'translated' => NULL, 'has_suggestion' => NULL),
@@ -1350,12 +1380,27 @@ function l10n_community_theme($existing, $type, $theme, $path) {
'l10n_community_filter_form' => array(
'arguments' => array('form' => NULL),
),
- 'l10n_community_translate_form' => array(
- 'arguments' => array('form' => NULL),
+ 'l10n_community_translate_translation' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'l10n_community_translate_actions' => array(
+ 'arguments' => array('element' => NULL),
),
'l10n_community_in_context' => array(
'arguments' => array('source' => NULL),
),
+ 'l10n_community_translate_radio' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'l10n_community_translate_source' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'l10n_community_translate_translation_list' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'l10n_community_translate_table' => array(
+ 'arguments' => array('element' => NULL),
+ ),
// l10n_community.admin.inc
'l10n_community_admin_projects_form' => array(
'arguments' => array('form' => NULL),
@@ -1371,77 +1416,6 @@ function l10n_community_theme($existing, $type, $theme, $path) {
}
/**
- * Theme a textual button.
- *
- * Text values are centralized here so it is easy to change.
- */
-function theme_l10n_community_button($type, $class, $extras = '') {
- switch ($type) {
- case 'translate':
- $text = t('Translate');
- break;
- case 'lookup':
- $text = t('Information');
- break;
- case 'edit':
- // Source string and translation edit field.
- $text = t('Edit');
- break;
- case 'has-suggestion':
- case 'has-no-suggestion':
- case 'untranslated':
- // Star in a filled circle.
- $text = t('Suggestions');
- break;
- case 'approve':
- // Checkmark.
- $text = t('Approve');
- break;
- case 'decline':
- // Checkmark.
- $text = t('Decline');
- break;
- case 'save':
- // Save button.
- $text = t('Save');
- break;
- case 'clear':
- // Clear form button.
- $text = t('Clear');
- break;
- }
- return ' ';
-}
-
-/**
- * Theme a list of translatable strings. Adds a copy button to each string
- * for quickly copying its source text into a translation form.
- */
-function theme_l10n_community_strings($items, $form = TRUE) {
- $output = "";
- foreach ($items as $i => $item) {
- $output .= "- ";
- if ($form && ($i < 1)) {
- // Only print copy button if we are displaying a form and on the first item.
- // For plurals, only the first item will have the copy button, but will copy
- // all the values into the form.
- $output .= "";
- }
- $output .= "". $item ."";
- $output .= "
";
- }
- $output .= "
";
- return $output;
-}
-
-/**
- * Copy button for string values.
- */
-function theme_l10n_community_copy_button() {
- return theme('l10n_community_button', 'edit', 'l10n-community-copy');
-}
-
-/**
* Format string for display. Takes plurals into account.
*/
function l10n_community_format_string($value, $rich_markup = TRUE) {
diff --git l10n_community/moderate.inc l10n_community/moderate.inc
index 1e94a5e..04fb316 100644
--- l10n_community/moderate.inc
+++ l10n_community/moderate.inc
@@ -23,7 +23,7 @@ function l10n_community_moderate_page($langcode) {
$filters = l10n_community_build_filter_values($_GET, TRUE);
$output = drupal_get_form('l10n_community_filter_form', $filters, TRUE);
-
+
$strings = l10n_community_get_suggestions($langcode, $filters);
if (!count($strings)) {
drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.'));
@@ -33,12 +33,12 @@ function l10n_community_moderate_page($langcode) {
drupal_set_title(t('Suggestions for @language', array('@language' => $languages[$langcode]->name)));
$output .= drupal_get_form('l10n_community_moderation_form', $strings, $languages[$langcode], $filters);
}
- return $output;
+ return $output;
}
// == Moderation form ==========================================================
-/**
+/**
* Translation web interface.
*
* @param $strings
@@ -51,7 +51,7 @@ function l10n_community_moderate_page($langcode) {
function l10n_community_moderation_form(&$form_state, $strings = array(), $language = NULL, $filters) {
$form['#redirect'] = array($_GET['q'], l10n_community_flat_filters($filters));
-
+
$form['pager'] = array(
'#value' => theme('pager', NULL, $filters['limit'], 0)
);
@@ -97,7 +97,7 @@ function l10n_community_moderation_form(&$form_state, $strings = array(), $langu
$form['strings']['translation'][$string->tid] = array(
//'#type' => 'item',
'#value' => l10n_community_format_string($string->translation, FALSE),
- );
+ );
$form['strings']['suggestion'][$string->tid] = array(
//'#type' => 'item',
'#value' => l10n_community_format_string($string->suggestion, FALSE),
@@ -156,13 +156,13 @@ function l10n_community_get_suggestions($langcode, $filters) {
$join[] = "INNER JOIN {l10n_community_string} s ON ts.sid = s.sid";
$where[] = "ts.is_suggestion = 1 AND ts.is_active = 1 AND ts.language = '%s'";
$where_args[] = $langcode;
-
+
// Add submitted by condition
if (!empty($filters['author'])) {
$where[] = "ts.uid_entered = %d";
$where_args[] = $filters['author']->uid;
}
-
+
// Restrict based on project or release.
$release = empty($filters['release']) || $filters['release'] === 'all' ? NULL : $filters['release'];
$project = $filters['project'];
@@ -173,7 +173,7 @@ function l10n_community_get_suggestions($langcode, $filters) {
// Release restriction.
$where_args[] = $release;
$where[] = 'l.rid = %d';
- }
+ }
elseif ($project) {
$where[] = "l.pid = %d";
$where_args[] = $project->pid;
@@ -208,7 +208,7 @@ function l10n_community_get_suggestions($langcode, $filters) {
$join[] = "LEFT JOIN {l10n_community_translation} tt ON tt.sid = ts.sid AND tt.is_suggestion = 0 AND tt.is_active = 1 AND tt.language = '%s'";
$join_args[] = $langcode;
}
-
+
// Only show strings matching the user's permissions. It should be already
// ensured that the user has either one of these permissions, when calling
// this function, or nothing will be returned.
@@ -241,7 +241,7 @@ function l10n_community_get_suggestions($langcode, $filters) {
*/
function l10n_community_moderate_approve_selected($langcode, $tids, $strings) {
global $user;
-
+
// In case we have more than one sugggestion for an sid, error.
$sids = array_unique($strings);
if (count($sids) < count($strings)) {
@@ -250,15 +250,15 @@ function l10n_community_moderate_approve_selected($langcode, $tids, $strings) {
}
$tid_placeholders = db_placeholders($tids);
$sid_placeholders = db_placeholders($sids);
-
+
// Mark existing translations and suggestions as inactive in this language.
$args = array_merge($sids, array($langcode));
db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid IN ($sid_placeholders) AND language = '%s'", $args);
-
+
// Remove placeholder translation record (which was there if
// first came suggestions, before an actual translation).
db_query("DELETE FROM {l10n_community_translation} WHERE sid IN ($sid_placeholders) AND translation = '' AND language = '%s'", $args);
-
+
// Mark this exact suggestions as active, and set approval time.
$args = array_merge(array(time(), $user->uid), $tids);
db_query("UPDATE {l10n_community_translation} SET time_approved = %d, uid_approved = %d, has_suggestion = 0, is_suggestion = 0, is_active = 1 WHERE tid IN ($tid_placeholders)", $args);
@@ -278,15 +278,15 @@ function l10n_community_moderate_decline_selected($langcode, $tids, $sids) {
// Deactive the selected suggestions.
db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE tid IN ($tid_placeholders)", $tids);
drupal_set_message(format_plural(db_affected_rows(), 'A suggestion has been declined.', '@count suggestions have been declined.'));
-
+
// Update 'has suggestion' option for remaining string translations
$args = array_merge(array($langcode), $sids);
$result = db_query(
- "SELECT tt.tid
- FROM {l10n_community_translation} tt
- LEFT JOIN {l10n_community_translation} ts ON tt.sid = ts.sid AND tt.language = ts.language AND ts.is_active = 1 AND ts.is_suggestion = 1
- WHERE tt.is_active = 1 AND tt.is_suggestion = 0 AND tt.has_suggestion = 1 AND tt.language = '%s' AND tt.sid IN ($sid_placeholders)
- GROUP BY tt.tid
+ "SELECT tt.tid
+ FROM {l10n_community_translation} tt
+ LEFT JOIN {l10n_community_translation} ts ON tt.sid = ts.sid AND tt.language = ts.language AND ts.is_active = 1 AND ts.is_suggestion = 1
+ WHERE tt.is_active = 1 AND tt.is_suggestion = 0 AND tt.has_suggestion = 1 AND tt.language = '%s' AND tt.sid IN ($sid_placeholders)
+ GROUP BY tt.tid
HAVING COUNT(ts.tid) = 0",
$args
);
@@ -304,7 +304,7 @@ function theme_l10n_community_moderation_form($form) {
$output = '';
$pager = isset($form['pager']) ? drupal_render($form['pager']) : '';
$output .= $pager;
- $output .= drupal_render($form['options']);
+ $output .= drupal_render($form['options']);
$header = array(theme('table_select_header_cell'), t('Suggestion'), t('Translation'), t('Source'));
foreach (element_children($form['strings']['tid']) as $tid) {
$rows[] = array(
diff --git l10n_community/translate.inc l10n_community/translate.inc
index 8d07552..b099c2d 100644
--- l10n_community/translate.inc
+++ l10n_community/translate.inc
@@ -6,50 +6,7 @@
* Translation view and editing pages for localization community.
*/
-// = Translation interface hub =================================================
-
-/**
- * Menu callback for the translation pages.
- *
- * Displays a translation view or translation edit page depending
- * on permissions. If no strings are found, an error is printed.
- *
- * @param $langcode
- * Language code, for example 'hu', 'pt-br', 'de', 'it'.
- */
-function l10n_community_translate_page($langcode = NULL, $mode = 'view') {
-
- // Add missing breadcrumb.
- drupal_set_breadcrumb(
- array(
- l(t('Home'), NULL),
- l(t('Translate'), 'translate')
- )
- );
-
- $languages = l10n_community_get_languages();
- $perm = l10n_community_get_permission($langcode);
-
- $filters = l10n_community_build_filter_values($_GET);
- $output = drupal_get_form('l10n_community_filter_form', $filters);
-
- $strings = l10n_community_get_strings($languages[$langcode]->language, $filters, $filters['limit']);
- if (!count($strings)) {
- drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.'));
- }
- elseif (!($perm & L10N_PERM_SUGGEST) || $mode == 'view') {
- // For users without permission to translate or suggest, display the view.
- drupal_set_title(t('@language translations', array('@language' => $languages[$langcode]->name)));
- $output .= l10n_community_translate_view($strings, $languages[$langcode], $filters);
- }
- else {
- // For users with some permission, display the form.
- drupal_add_js(drupal_get_path('module', 'l10n_community') .'/l10n_community.js');
- drupal_set_title(t('Translate to @language', array('@language' => $languages[$langcode]->name)));
- $output .= drupal_get_form('l10n_community_translate_form', $strings, $languages[$langcode], $filters, $perm);
- }
- return $output;
-}
+module_load_include('inc', 'l10n_community', 'api');
// = Filter form handling ======================================================
@@ -74,7 +31,7 @@ function l10n_community_filter_form(&$form_state, $filters, $limited = FALSE) {
L10N_STATUS_NO_SUGGESTION => t('Has no suggestion'),
L10N_STATUS_HAS_SUGGESTION => t('Has suggestion'),
);
-
+
$form['project'] = array(
'#title' => t('Project'),
'#default_value' => isset($filters['project']) ? $filters['project']->title : '',
@@ -190,386 +147,431 @@ function l10n_community_filter_form_submit($form, &$form_state) {
}
}
-// = Translation viewer ========================================================
-
/**
- * Form for translations display.
- *
- * @param $strings
- * Array of string objects to display on the page.
- * @param $language
- * Language object corresponding to the page displayed.
- * @param $filters
- * Filters used to present this listing view.
+ * Theme function for l10n_community_filter_form.
*/
-function l10n_community_translate_view($strings = array(), $language = NULL, $filters = array()) {
- $output = '';
- $rows = array();
- foreach ($strings as $string) {
- $row = array();
- // Source display
- $source = l10n_community_format_string($string->value);
- $source .= theme('l10n_community_in_context', $string);
- $row[] = array('data' => $source, 'class' => 'source');
-
- // Translation display.
- if (!empty($string->translation)) {
- if (strpos($string->value, chr(0)) !== FALSE) {
- $translations = explode(chr(0), l10n_community_format_text($string->translation));
- // Fill in any missing items, so it is shown that not all items are done.
- if (count($translations) < $language->plurals) {
- $translations = array_merge($translations, array_fill(0, count($translations) - $language->plurals, ''));
- }
- $translation = theme('item_list', $translations);
- }
- else {
- $translation = l10n_community_format_text($string->translation);
- }
- $row[] = $translation;
- }
- else {
- $row[] = '';
+function theme_l10n_community_filter_form($form) {
+ $row = array();
+ $labels = array();
+ // Only display these elements in distinct table cells
+ $elements = array('project', 'release', 'context', 'status', 'author', 'search', 'limit');
+ foreach ($form as $id => &$element) {
+ if (in_array($id, $elements)) {
+ $labels[] = $element['#title'];
+ unset($element['#title']);
+ $row[] = drupal_render($element);
}
- $rows[] = $row;
}
- $output .= ($pager = theme('pager', NULL, $filters['limit'], 0));
- $output .= theme('table', array(t('Source Text'), t('Translations')), $rows, array('class' => 'l10n-server-translate'));
- $output .= $pager;
- $output = "". $output ."";
- return $output;
+ // Fill in the rest of the header above the buttons.
+ $labels[] = '';
+ // Display the rest of the form in the last cell
+ $row[] = array('data' => drupal_render($form), 'class' => 'last');
+ return theme('table', $labels, array($row), array('class' => 'l10n-server-filter'));
}
-// = Translation editor ========================================================
+// = Translation view ==========================================================
/**
- * Translation web interface.
- *
- * @param $strings
- * Array of string objects to display.
- * @param $language
- * Language object.
- * @param $filters
- * Filters used to present this editing view.
- * @param $perm
- * Community permission level of user watching the page.
+ * Menu callback: List translations and suggestions
*/
-function l10n_community_translate_form(&$form_state, $strings = array(), $language = NULL, $filters = array(), $perm = L10N_PERM_SUGGEST) {
-
- if (isset($_GET['page'])) {
- // Ensure that we keep all filter values, even the page number, so
- // after submission, the same page can be shown.
- $filters['page'] = (int) $_GET['page'];
- }
+function l10n_community_translate_page($langcode) {
+ drupal_add_css(drupal_get_path('module', 'l10n_community') .'/editor.css');
+ drupal_add_js(drupal_get_path('module', 'l10n_community') .'/jquery.worddiff.js');
+ drupal_add_js(drupal_get_path('module', 'l10n_community') .'/editor.js');
- $form = array(
- '#tree' => TRUE,
- '#redirect' => array($_GET['q'], l10n_community_flat_filters($filters))
- );
- $form['pager_top'] = array(
- '#weight' => -10,
- '#value' => ($pager = theme('pager', NULL, $filters['limit'], 0)),
- );
- $form['pager_bottom'] = array(
- '#weight' => 10,
- '#value' => $pager,
- );
- // Keep language code and URI in form for further reference.
- $form['langcode'] = array(
- '#type' => 'value',
- '#value' => $language->language
- );
- $form['project'] = array(
- '#type' => 'value',
- '#value' => isset($project) ? $project->uri : NULL
- );
+ $language = l10n_community_get_language($langcode);
+ $filters = l10n_community_build_filter_values($_GET);
+ $strings = l10n_community_get_strings($language->language, $filters, $filters['limit']);
- foreach ($strings as $string) {
- $form[$string->sid] = array(
- '#tree' => TRUE,
- );
+ // Set the most appropriate title.
+ if ($filters['project']) {
+ drupal_set_title(t('Translate %project to @language', array('%project' => $filters['project']->title, '@language' => $language->name)));
+ }
+ else {
+ drupal_set_title(t('Translate to @language', array('@language' => $language->name)));
+ }
- // A toolbox which displays action icons on each string editor fieldset.
- $toolbox = theme('l10n_community_button', 'translate', 'l10n-translate active');
- $toolbox .= theme('l10n_community_button', 'lookup', 'l10n-lookup');
- $toolbox .= $string->has_suggestion ? theme('l10n_community_button', 'has-suggestion', 'l10n-suggestions') : "";
- $toolbox = "$toolbox";
- $form[$string->sid]['toolbox'] = array(
- '#type' => 'markup',
- '#value' => $toolbox,
- );
- $form[$string->sid]['messagebox'] = array(
- '#type' => 'markup',
- '#value' => "",
- );
+ // Add the filter form.
+ $output = drupal_get_form('l10n_community_filter_form', $filters);
- $is_plural = strpos($string->value, "\0");
- // Multiple source strings if we deal with plurals. The form item and
- // consequently the JavaScript strings identifiers are the sid and then
- // the index of the plural being displayed.
- $string_parts = explode(chr(0), $string->value);
- foreach ($string_parts as $delta => &$part) {
- $part = l10n_community_format_text($part, $string->sid, (count($string_parts) > 1) ? $delta : NULL);
- }
- $source = theme('l10n_community_strings', $string_parts);
- $source .= theme('l10n_community_in_context', $string);
+ // Output the actual strings.
+ if (!count($strings)) {
+ drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.'));
+ }
+ else {
+ $output .= drupal_get_form('l10n_community_translate_form', $language, $filters, $strings);
+ }
- $form[$string->sid]['source'] = array(
- '#type' => 'item',
- '#value' => $source,
- );
+ return $output;
+}
- $translated = !empty($string->translation);
- $form[$string->sid]['translation'] = array(
- '#type' => 'item',
- // Hide editing controls of translated stuff to save some space and guide user eyes.
- '#prefix' => '',
- '#suffix' => '',
- );
+/**
+ * Form callback: List translations and suggestions.
+ *
+ * @param $form_state
+ * The form state array.
+ * @param $language
+ * A language object.
+ * @param $filters
+ * An array of filters applied to the strings.
+ * @param $strings
+ * The strings to render.
+ */
+function l10n_community_translate_form(&$form_state, $language, $filters, $strings) {
+ $permission = l10n_community_get_permission($language->language);
+ $pager = theme('pager', NULL, $filters['limit'], 0);
+ $redirect_url = $_GET;
+ unset($redirect_url['q']);
- if ($is_plural) {
+ $form = array(
+ '#submit' => array('l10n_community_translate_submit'),
+ '#redirect' => array($_GET['q'], $redirect_url),
+ 'langcode' => array('#type' => 'value', '#value' => $language->language),
+ 'pager_top' => array('#weight' => -10, '#value' => $pager),
+ 'submit_top' => array('#type' => 'submit', '#value' => t('Save changes'), '#access' => $permission & L10N_PERM_SUGGEST),
+ 'strings' => array('#tree' => TRUE, '#theme' => 'l10n_community_translate_table'),
+ 'submit' => array('#type' => 'submit', '#value' => t('Save changes'), '#access' => $permission & L10N_PERM_SUGGEST),
+ 'pager_bottom' => array('#weight' => 10, '#value' => $pager),
+ );
- // Dealing with a string with plural versions.
- if ($translated) {
- // Add translation form element with all plural versions.
- $translations = explode("\0", $string->translation);
- $string_parts = array();
- for ($i = 0; $i < $language->plurals; $i++) {
- $target = $string->sid .'-'. $i;
- $string_parts[] = l10n_community_format_text($translations[$i], $string->sid, $i);
- }
- $form[$string->sid]['translation_existing'] = array(
- '#type' => 'item',
- '#value' => theme('l10n_community_strings', $string_parts),
- );
- }
+ foreach ($strings as $string) {
+ $form['strings'][$string->sid] = _l10n_community_translate_string($form_state, $string, $language, $permission);
+ }
- $string_parts = explode(chr(0), $string->value);
+ return $form;
+}
- for ($i = 0; $i < $language->plurals; $i++) {
- $target = $string->sid .'-'. $i;
- if ($translated) {
- // Already translated so we ask for new translation or suggestion.
- $description = !($perm & L10N_PERM_MODERATE_OWN) ? t('New suggestion for variant #%d', array('%d' => $i)) : t('New translation for variant #%d', array('%d' => $i));
- }
- else {
- // Not translated yet, so we ask for initial translation or suggestion.
- $description = !($perm & L10N_PERM_MODERATE_OWN) ? t('Suggestion for variant #%d', array('%d' => $i)) : t('Translation for variant #%d', array('%d' => $i));
- }
+/**
+ * Return a marked-up string.
+ */
+function _l10n_community_translate_render_strings($strings, $empty = '') {
+ if ($empty) {
+ $empty = ' data-empty="'. check_plain($empty) .'"';
+ }
+ return "". implode("
", array_map('check_plain', $strings)) .'';
+}
- // Include editing area for each plural variant.
- $source_index = ($i > 0 ? 1 : 0);
- $form[$string->sid]['translation']['value'][$i] = array(
- // Use textarea for long and multiline strings.
- '#type' => ((strlen($string_parts[$source_index]) > 45) || (count(explode("\n", $string_parts[$source_index])) > 1)) ? 'textarea' : 'textfield',
- '#description' => $description,
- '#rows' => 1,
- '#id' => 'l10n-community-translation-'. $target,
- );
- }
- }
+/**
+ * Creates the form fragment for a source string.
+ */
+function _l10n_community_translate_string(&$form_state, $source, $language, $permission) {
+ // Normalize empty default translation.
+ if (!$source->translation) {
+ $source->tid = '0';
+ $source->translation = array(t('(not translated)'));
+ $source->is_active = '1';
+ $source->is_suggestion = '0';
+ }
+ else {
+ $source->translation = l10n_community_unpack_string($source->translation);
+ }
- // Dealing with a simple string (no plurals).
+ $source->value = l10n_community_unpack_string($source->value);
- else {
- if ($translated) {
- $form[$string->sid]['translation_existing'] = array(
- '#type' => 'item',
- '#value' => theme('l10n_community_strings', array(l10n_community_format_text($string->translation, $string->sid))),
- );
- }
- $form[$string->sid]['translation']['value'] = array(
- // Use textarea for long and multiline strings.
- '#type' => ((strlen($string->value) > 45) || (count(explode("\n", $string->value)) > 1)) ? 'textarea' : 'textfield',
- // Provide accurate title based on previous data and permission.
- '#description' => $translated ? (!($perm & L10N_PERM_MODERATE_OWN) ? t('Add a new suggestion') : t('Add a new translation')) : (!($perm & L10N_PERM_MODERATE_OWN) ? t('Suggestion') : ''),
- '#rows' => 4,
- '#resizable' => FALSE,
- '#cols' => NULL,
- '#size' => NULL,
- '#id' => 'l10n-community-translation-'. $string->sid,
- );
- if (strlen($string->value) > 200) {
- $form[$string->sid]['translation']['value']['#rows'] = floor(strlen($string->value) * .03);
- $form[$string->sid]['translation']['value']['#resizable'] = TRUE;
- }
- }
+ $form = array(
+ '#string' => $source,
+ '#langcode' => $language->language,
+ 'source' => array(
+ 'string' => array('#value' => _l10n_community_translate_render_strings($source->value)),
+ ),
+ );
- // Add AJAX saving buttons
- $form[$string->sid]['translation']['save'] = array(
- '#prefix' => "",
- '#value' => theme('l10n_community_button', 'clear', 'l10n-clear'),
- '#type' => 'markup',
+ if ($permission & L10N_PERM_SUGGEST) {
+ $form['source']['edit'] = array(
+ '#value' => t('Edit Copy'),
+ '#prefix' => '',
);
+ }
- if (!($perm & L10N_PERM_MODERATE_OWN)) {
- // User with suggestion capability only, record this.
- $form[$string->sid]['translation']['is_suggestion'] = array(
- '#type' => 'value',
- '#value' => TRUE
- );
- }
- else {
- // User with full privileges, offer option to submit suggestion.
- $form[$string->sid]['translation']['is_suggestion'] = array(
- '#title' => t('Suggestion for discussion'),
- '#type' => 'checkbox',
- );
+ // Add the current string (either a approved translation or a mock object
+ // for the "untranslated" string).
+ $form[$source->tid] = _l10n_community_translate_translation($form_state, $source, $permission, $source);
+
+ // When there are suggestions, load them from the database.
+ if ($source->has_suggestion) {
+ $result = db_query("SELECT t.tid, t.sid, t.translation, t.uid_entered, t.time_entered, t.is_active, t.is_suggestion, u.name as username FROM {l10n_community_translation} t LEFT JOIN {users} u ON u.uid = t.uid_entered WHERE t.language = '%s' AND t.sid = %d AND t.is_active = 1 AND t.is_suggestion = 1 ORDER BY t.time_entered", $language->language, $source->sid);
+ while ($suggestion = db_fetch_object($result)) {
+ $suggestion->translation = l10n_community_unpack_string($suggestion->translation);
+ // Add the suggestion to the list.
+ $form[$suggestion->tid] = _l10n_community_translate_translation($form_state, $suggestion, $permission, $source);
}
}
- // Add all strings for copy-pasting and some helpers.
- drupal_add_js(
- array(
- 'l10n_lookup_help' => t('Show detailed information.'),
- 'l10n_approve_error' => t('There was an error approving this suggestion. You might not have permission or the suggestion id was invalid.'),
- 'l10n_approve_confirm' => t('!icon Suggestion approved.', array('!icon' => '✔')),
+ // If the user may add new suggestions, display a textarea.
+ if ($permission & L10N_PERM_SUGGEST) {
+ $textarea = _l10n_community_translate_translation_textarea($source, $language);
+ $form[$textarea->tid] = _l10n_community_translate_translation($form_state, $textarea, $permission, $source);
+ }
- 'l10n_decline_error' => t('There was an error declining this suggestion. You might not have permission or the suggestion id was invalid.'),
- 'l10n_decline_confirm' => t('Suggestion declined.'),
+ return $form;
+}
- 'l10n_details_callback' => url('translate/details/'. $language->language .'/'),
- 'l10n_suggestions_callback' => url('translate/suggestions/'. $language->language .'/'),
- 'l10n_approve_callback' => url('translate/approve/'),
- 'l10n_decline_callback' => url('translate/decline/'),
- 'l10n_form_token_path' => variable_get('clean_url', '0') ? '?form_token=' : '&form_token=',
- 'l10n_num_plurals' => $language->plurals
- ),
- 'setting'
- );
+/**
+ * Build mock object for new textarea.
+ */
+function _l10n_community_translate_translation_textarea($source, $language) {
+ global $user;
- // Let the user submit the form.
- $form['submit'] = array(
- '#type' => 'submit',
- '#value' => !($perm & L10N_PERM_MODERATE_OWN) ? t('Save suggestions') : t('Save translations')
+ return (object)array(
+ 'sid' => $source->sid,
+ 'tid' => 'new',
+ 'translation' => array_fill(0, count($source->value), ''),
+ 'is_active' => '1',
+ 'is_suggestion' => '1',
+ 'uid_entered' => $user->uid,
);
+}
- $form['#theme'] = 'l10n_community_translate_form';
+/**
+ * Generates the byline containing meta information about a string.
+ */
+function l10n_community_translate_byline($string) {
+ $params = array(
+ '!author' => theme('username', (object)array('name' => $string->username, 'uid' => $string->uid_entered)),
+ '@date' => format_date($string->time_entered),
+ '@ago' => t('@time ago', array('@time' => format_interval(time() - $string->time_entered))),
+ );
- return $form;
+ if (!empty($string->uid_approved) && $string->uid_entered === $string->uid_approved && $string->time_entered === $string->time_approved) {
+ return t('translated and approved by !author on @date', $params);
+ }
+ else {
+ $title = t('suggested by !author on @date', $params);
+ if (!empty($string->uid_approved)) {
+ $title .= '
'. t('approved by !author on @date', array(
+ '!author' => theme('username', (object)array('name' => $string->username_approved, 'uid' => $string->uid_approved)),
+ '@date' => format_date($string->time_approved),
+ '@ago' => t('@time ago', array('@time' => format_interval(time() - $string->time_approved))),
+ ));
+ }
+ return $title;
+ }
}
/**
- * Save translations entered in the web form.
+ * Creates the form fragment for a translated string.
*/
-function l10n_community_translate_form_submit($form, &$form_state) {
+function _l10n_community_translate_translation(&$form_state, $string, $permission, $source) {
global $user;
+ $is_own = $user->uid == $string->uid_entered;
+ $is_active = $string->is_active && !$string->is_suggestion;
+ $is_new = $string->tid == 'new';
+ $may_moderate = $permission & ($is_own ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS);
+ //$may_stabilize = $permission & ($is_own ? L10N_PERM_STABILIZE_OWN : L10N_PERM_STABILIZE_OTHERS);
- $inserted = $updated = $unchanged = $suggested = $duplicates = $ignored = 0;
+ $form = array(
+ '#theme' => 'l10n_community_translate_translation',
+ 'original' => array('#type' => 'value', '#value' => $string),
+ );
- foreach ($form_state['values'] as $sid => $item) {
- if (!is_array($item) || !isset($item['translation'])) {
- // Skip, if we don't have translations in this form item,
- // which means this is some other form value.
- continue;
- }
+ $form['active'] = array(
+ '#type' => 'radio',
+ '#theme' => 'l10n_community_translate_radio',
+ '#title' => _l10n_community_translate_render_strings($string->translation, $is_new ? t('(empty)') : FALSE),
+ '#return_value' => $string->tid,
+ '#default_value' => $is_active ? $string->tid : NULL,
+ '#parents' => array('strings', $string->sid, 'active'),
+ '#disabled' => !$may_moderate && !$is_active,
+ '#attributes' => array('class' => 'selector'),
+ );
- $source_string = db_result(db_query('SELECT value FROM {l10n_community_string} WHERE sid = %d', $sid));
- $text = '';
- if (is_string($item['translation']['value']) && strlen(trim($item['translation']['value']))) {
- // Single string representation: simple translation.
- $text = l10n_community_trim($item['translation']['value'], $source_string);
+ if ($string->tid) {
+ if ($may_moderate && $string->tid != 'new') {
+ $form['declined'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Declined'),
+ '#default_value' => !($string->is_active || $string->is_suggestion),
+ );
}
- if (is_array($item['translation']['value'])) {
- // Array -> plural variants are provided. Join them with a NULL separator.
- $text = join("\0", $item['translation']['value']);
- if (trim($text) == '') {
- // If the whole string only contains NULL bytes, empty the string, so
- // we don't save an empty translation. Otherwise the NULL bytes need
- // to be there, so we know plural variant indices.
- $text = '';
- }
+ // if ($may_stabilize) {
+ // $form['stable'] = array(
+ // '#type' => 'checkbox',
+ // '#title' => t('Stable'),
+ // '#default_value' => FALSE, // $string->is_stable,
+ // );
+ // }
+ if ($string->tid == 'new') {
+ $form['value'] = array_fill(0, count($source->value), array(
+ '#type' => 'textarea',
+ '#cols' => 60,
+ '#rows' => 3,
+ '#default_value' => t(''),
+ ));
}
+ else {
+ if ($permission & L10N_PERM_SUGGEST) {
+ $form['edit'] = array(
+ '#value' => t('Edit Copy'),
+ '#prefix' => '',
+ );
+ }
+ if (isset($string->username)) {
+ $title = l10n_community_translate_byline($string);
- if (!empty($text)) {
- // Check for duplicate translation or suggestion.
- if (l10n_community_is_duplicate($text, $sid, $form_state['values']['langcode'])) {
- $duplicates++;
- continue;
+ $form['author'] = array(
+ '#value' => $title,
+ );
}
+ }
+ }
- // We have some string to save.
- l10n_community_target_save(
- $sid, $text, $form_state['values']['langcode'], $user->uid,
- ($item['translation']['is_suggestion'] == TRUE),
- $inserted, $updated, $unchanged, $suggested
- );
+ return $form;
+}
+
+function theme_l10n_community_translate_actions($element) {
+ $actions = '';
+ foreach (array('declined', /*'stable', */'edit') as $type) {
+ if (isset($element[$type])) {
+ $actions .= ''. drupal_render($element[$type]) .' ';
}
}
+ if (!empty($actions)) {
+ return ''. $actions .'
';
+ }
+ else {
+ return '';
+ }
+}
+
+function theme_l10n_community_translate_translation($element) {
+ if (!isset($element['#attributes']['class'])) {
+ $element['#attributes']['class'] = '';
+ }
+ $element['#attributes']['class'] .= ' clearfix translation';
+
+ switch ($element['active']['#return_value']) {
+ case 'new':
+ $element['#attributes']['class'] .= ' new-translation';
+ break;
+ case '0':
+ $element['#attributes']['class'] .= ' no-translation';
+ // Fallthrough.
+ default:
+ if ($element['active']['#value'] !== '') {
+ $element['#attributes']['class'] .= ' is-active default';
+ }
+ }
+
+ $output = '';
+ $output .= theme('l10n_community_translate_actions', $element);
+ $output .= drupal_render($element['active']);
+
+ if (isset($element['author'])) {
+ $output .= '';
+ }
+
+ if (isset($element['value'])) {
+ $output .= drupal_render($element['value']);
+ }
- // Inform user about changes made to the database.
- l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored);
+ return $output . ' ';
}
-// = Theme functions ===========================================================
+function theme_l10n_community_translate_radio($element) {
+ _form_set_class($element, array('form-radio'));
+ $output = '';
+
+ if (isset($element['#title'])) {
+ $output .= '';
+ }
-/**
- * Theme function for l10n_community_filter_form.
- */
-function theme_l10n_community_filter_form($form) {
- $row = array();
- $labels = array();
- // Only display these elements in distinct table cells
- $elements = array('project', 'release', 'context', 'status', 'author', 'search', 'limit');
- foreach ($form as $id => &$element) {
- if (in_array($id, $elements)) {
- $labels[] = $element['#title'];
- unset($element['#title']);
- $row[] = drupal_render($element);
+ return $output;
+}
+
+function theme_l10n_community_translate_translation_list($element) {
+ $output = '';
+ foreach (element_children($element) as $child) {
+ if (is_numeric($child) || $child == 'new') {
+ $output .= drupal_render($element[$child]);
}
}
- // Fill in the rest of the header above the buttons.
- $labels[] = '';
- // Display the rest of the form in the last cell
- $row[] = array('data' => drupal_render($form), 'class' => 'last');
- return theme('table', $labels, array($row), array('class' => 'l10n-server-filter'));
+ $output .= '
';
+
+ return $output;
}
-/**
- * Theme function for l10n_community_translate_form.
- */
-function theme_l10n_community_translate_form($form) {
+function theme_l10n_community_translate_source($element) {
+ $output = theme('l10n_community_translate_actions', $element['source']);
+ $output .= '';
+ $output .= theme('l10n_community_in_context', $element['#string']);
+ $output .= '';
+ return $output;
+}
+
+function theme_l10n_community_translate_table($element) {
+ $header = array(
+ t('Source Text'),
+ t('Translations'),
+ );
+
$rows = array();
- $output = '';
+ foreach (element_children($element) as $key) {
+ $rows[] = array(
+ array('class' => 'source', 'data' => theme('l10n_community_translate_source', $element[$key])),
+ array('class' => 'translation', 'data' => theme('l10n_community_translate_translation_list', $element[$key])),
+ );
+ }
- foreach ($form as $id => &$element) {
- // if the form id is numeric, this form element is for editing a string
- if (is_numeric($id)) {
- $source_pane = drupal_render($element['source']);
- $translation_pane = "";
- $translation_pane .= drupal_render($element['toolbox']);
- $translation_pane .= "";
- $translation_pane .= !empty($element['translation_existing']) ? drupal_render($element['translation_existing']) : '';
- $translation_pane .= drupal_render($element['messagebox']);
- $translation_pane .= drupal_render($element['translation']) .'';
- $translation_pane .= "";
- $translation_pane .= "";
- $row = array(
- array(
- 'data' => $source_pane,
- 'class' => 'source',
- 'id' => 'spane-'. $id,
- ),
- array(
- 'data' => $translation_pane,
- 'class' => 'translation',
- 'id' => 'tpane-'. $id,
- ),
- );
- $rows[] = $row;
- unset($form[$id]);
+ return theme('table', $header, $rows, array('class' => 'l10n-table'));
+}
+
+function l10n_community_translate_submit($form, &$form_state) {
+ $langcode = $form_state['values']['langcode'];
+
+ foreach ($form_state['values']['strings'] as $sid => $string) {
+ foreach ($string as $tid => $options) {
+ // Store new suggestion.
+ $empty_values = 0;
+ if (isset($options['value']) && is_array($options['value'])) {
+ foreach ($options['value'] as $key => $value) {
+ if ($value === t('')) {
+ $options['value'] = '';
+ $empty_values++;
+ }
+ }
+ if ($tid === 'new' && count($options['value']) > $empty_values) {
+ $tid = l10n_community_add_suggestion($langcode, $sid, $options['value']);
+ if ($tid) {
+ l10n_community_counter('added');
+ if ($string['active'] === 'new') {
+ $string['active'] = $tid;
+ }
+ }
+ }
+ }
+
+ if (is_numeric($tid) && $tid > 0) {
+ if ($tid == $string['active']) {
+ if ($options['original']->is_suggestion) {
+ l10n_community_approve_string($langcode, $sid, $tid);
+ l10n_community_counter('approved');
+ }
+ // if (/*!$options['original']->is_stable && */!empty($options['stable'])) {
+ // dsm('mark as stable');
+ // l10n_community_counter('stabilized');
+ // }
+ }
+ elseif (!empty($options['declined'])) {
+ // also remove stable flag!
+ l10n_community_counter($options['original']->is_suggestion ? 'suggestion_declined' : 'declined');
+ l10n_community_decline_string($langcode, $sid, $tid);
+ }
+ }
}
}
- $output .= drupal_render($form['pager_top']);
- $output .= theme('table', array(t('Source Text'), t('Translations')), $rows, array('class' => 'l10n-server-translate'));
- $output .= drupal_render($form);
- return $output;
+
+ l10n_community_update_message();
}
+// = Miscellaneous =============================================================
+
/**
* Theme context information for source strings.
*
@@ -615,10 +617,12 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) {
$join = $join_args = $where = $where_args = array();
$sql = $sql_count = '';
- $select = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active FROM {l10n_community_string} s";
+ $select = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active, u.name as username, u2.name as username_approved FROM {l10n_community_string} s";
$select_count = "SELECT COUNT(DISTINCT(s.sid)) FROM {l10n_community_string} s";
$join[] = "LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0";
$join_args[] = $langcode;
+ $join[] = "LEFT JOIN {users} u ON u.uid = t.uid_entered";
+ $join[] = "LEFT JOIN {users} u2 ON u2.uid = t.uid_approved";
// Add submitted by condition
if (!empty($filters['author'])) {
@@ -636,7 +640,7 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) {
// Release restriction.
$where_args[] = $release;
$where[] = 'l.rid = %d';
- }
+ }
elseif ($project) {
$where[] = "l.pid = %d";
$where_args[] = $project->pid;
@@ -659,18 +663,18 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) {
// Restriction based on string status by translation / suggestions.
$status_sql = '';
- if ($filters['status'] & L10N_STATUS_UNTRANSLATED) {
+ if (isset($filters['status']) && $filters['status'] & L10N_STATUS_UNTRANSLATED) {
// We are doing a LEFT JOIN especially to look into the case, when we have nothing
// to match in the translation table, but we still have the string. (We get our
// records in the result set in this case). The translation field is empty or
// NULL in this case, as we are not allowing NULL there and only saving an empty
// translation if there are suggestions but no translation yet.
$where[] = "(t.translation is NULL OR t.translation = '')";
- }
+ }
elseif ($filters['status'] & L10N_STATUS_TRANSLATED) {
$where[] = "t.translation != ''";
}
- if ($filters['status'] & L10N_STATUS_HAS_SUGGESTION) {
+ if (isset($filters['status']) && $filters['status'] & L10N_STATUS_HAS_SUGGESTION) {
// Note that we are not searching in the suggestions themselfs, only
// the source and active translation values. The user interface underlines
// that we are looking for strings which have suggestions, not the
@@ -732,9 +736,9 @@ function l10n_community_build_filter_values($params, $suggestions = FALSE) {
'context' => isset($params['context']) ? (string) $params['context'] : 'all',
'limit' => (isset($params['limit']) && in_array($params['limit'], array(5, 10, 20, 30))) ? (int) $params['limit'] : 10,
);
-
- // The project can be a dropdown or text field depending on number of
- // projects. So we need to sanitize its value.
+
+ // The project can be a dropdown or text field depending on number of
+ // projects. So we need to sanitize its value.
if (isset($params['project'])) {
// Try to load project by uri or title, but give URI priority. URI is used
// to shorten the URL and have simple redirects. Title is used if the
@@ -756,6 +760,152 @@ function l10n_community_build_filter_values($params, $suggestions = FALSE) {
}
/**
+ * Ensures that there is a mock translation for a given language/string.
+ *
+ * @param $langcode
+ * The language to be checked for a mock translation.
+ * @param $sid
+ * The string ID that needs to have a mock translation.
+ */
+function l10n_community_mock_translation($langcode, $sid) {
+ if (!db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode))) {
+ // Insert mock tuple that acts as placeholder.
+ db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '', '%s', 0, 0, 0, 1)", $sid, $langcode);
+ }
+}
+
+/**
+ * Adds a suggestion to a language/string.
+ *
+ * @param $langcode
+ * The language of the new translation.
+ * @param $sid
+ * The string ID for which a new translation should be added.
+ * @param $translation
+ * An array of strings which constitute the new translation.
+ */
+function l10n_community_add_suggestion($langcode, $sid, $translation) {
+ global $user;
+
+ // Load source string and adjust translation whitespace based on source.
+ $source_string = db_result(db_query('SELECT value FROM {l10n_community_string} WHERE sid = %d', $sid));
+ $translation = l10n_community_pack_string($translation);
+ $translation = l10n_community_trim($translation, $source_string);
+
+ // Don't store empty translations.
+ if ($translation === '') {
+ return NULL;
+ }
+
+ // Look for an existing active translation, if any.
+ $existing = db_fetch_object(db_query("SELECT tid FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND translation = '%s'", $sid, $langcode, $translation));
+ if (!empty($existing)) {
+ // The translation is already in the db. Make it an active suggestion again.
+ db_query("UPDATE {l10n_community_translation} SET is_suggestion = 1, is_active = 1 WHERE tid = %d", $existing->tid);
+ $tid = $existing->tid;
+ }
+ else {
+ // This is a new translation.
+ l10n_community_mock_translation($langcode, $sid);
+ // Insert the new suggestion.
+ db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, 1, 1)", $sid, $translation, $langcode, $user->uid, time());
+ $tid = db_last_insert_id('l10n_community_translation', 'tid');
+ }
+
+ // Mark the existing or mock translation has having suggestions.
+ l10n_community_update_suggestion_status($langcode, $sid);
+
+ return $tid;
+}
+
+/**
+ * Marks a translation as declined.
+ *
+ * @param $langcode
+ * The language of the declined translation.
+ * @param $sid
+ * The string ID the translation belongs to.
+ * @param $tid
+ * The translation ID of the translation.
+ */
+function l10n_community_decline_string($langcode, $sid, $tid) {
+ // Mark this translation as inactive.
+ db_query("UPDATE {l10n_community_translation} SET is_suggestion = 0, is_active = 0 WHERE tid = %d", $tid);
+
+ // Make sure the mock translation is there in case we declined the active translation.
+ l10n_community_mock_translation($langcode, $sid);
+ l10n_community_update_suggestion_status($langcode, $sid);
+}
+
+/**
+ * Updates the has_suggestion flag for the active translation.
+ *
+ * @param $langcode
+ * The language of the string.
+ * @param $sid
+ * The string ID that should be updated.
+ */
+function l10n_community_update_suggestion_status($langcode, $sid) {
+ // Let's see if we have any suggestions remaining in this language.
+ $count = db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation}
+ WHERE sid = %d AND is_suggestion = 1 AND is_active = 1 AND language = '%s'", $sid, $langcode));
+
+ // Update the status according to the number of suggestions.
+ db_query("UPDATE {l10n_community_translation} SET has_suggestion = %d
+ WHERE sid = %d AND is_suggestion = 0 AND is_active = 1 AND language = '%s'", $count ? 1 : 0, $sid, $langcode);
+}
+
+/**
+ * Marks a translation as approve.
+ *
+ * @param $langcode
+ * The language of the approved translation.
+ * @param $sid
+ * The string ID the translation belongs to.
+ * @param $tid
+ * The translation ID of the translation.
+ */
+function l10n_community_approve_string($langcode, $sid, $tid) {
+ global $user;
+
+ // Remove placeholder translation record (which was there if
+ // first came suggestions, before an actual translation).
+ db_query("DELETE FROM {l10n_community_translation} WHERE sid = %d AND translation = '' AND language = '%s'", $sid, $langcode);
+
+ // Make the existing approved string a suggestion.
+ db_query("UPDATE {l10n_community_translation} SET is_suggestion = 1 WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode);
+
+ // Mark this exact suggestion as active, and set approval time.
+ db_query("UPDATE {l10n_community_translation} SET time_approved = %d, uid_approved = %d, is_suggestion = 0, is_active = 1 WHERE tid = %d;", time(), $user->uid, $tid);
+ l10n_community_update_suggestion_status($langcode, $sid);
+}
+
+/**
+ * Unpacks a string as retrieved from the database.
+ *
+ * @param $string
+ * The string with separation markers (NULL byte)
+ * @return
+ * An array of strings with one element for each plural form in case of
+ * a plural string, or one element in case of a regular string.
+ */
+function l10n_community_unpack_string($string) {
+ return explode("\0", $string);
+}
+
+/**
+ * Packs a string for storage in the database.
+ *
+ * @param $string
+ * An array of strings.
+ * @return
+ * A packed string with NULL bytes separating each string.
+ */
+function l10n_community_pack_string($strings) {
+ return implode("\0", $strings);
+}
+
+/**
* Replace complex data filters (objects or arrays) with string representations.
*
* @param $filters
@@ -771,3 +921,49 @@ function l10n_community_flat_filters($filters) {
}
return $filters;
}
+
+
+// = AJAX callbacks ============================================================
+
+/**
+ * Return a HTML list of projects, releases and counts of where strings
+ * appear in the managed projects.
+ *
+ * We could have been provided much more information, but usability should
+ * also be kept in mind. It is possible to investigate hidden information
+ * sources though, like tooltips on the release titles presented.
+ *
+ * This callback is invoked from JavaScript and is used as an AHAH provider.
+ *
+ * @param $langcode
+ * Language code.
+ * @param $sid
+ * Source string id.
+ */
+function l10n_community_string_details($langcode = NULL, $sid = 0) {
+ // Prevent devel module information.
+ $GLOBALS['devel_shutdown'] = FALSE;
+
+ // List of project releases, where this string is used.
+ $result = db_query('SELECT l.pid, p.title project_title, l.rid, r.title release_title, COUNT(l.lineno) as occurance_count FROM {l10n_community_line} l INNER JOIN {l10n_community_project} p ON l.pid = p.pid INNER JOIN {l10n_community_release} r ON l.rid = r.rid WHERE l.sid = %d AND p.status = 1 GROUP BY l.rid ORDER BY l.pid, l.rid', $sid);
+
+ $list = array();
+ $output = array();
+ $previous_project = '';
+ while ($instance = db_fetch_object($result)) {
+ if ($instance->project_title != $previous_project) {
+ if (!empty($list)) {
+ $output[] = join(', ', $list);
+ }
+ $list = array(''. $instance->project_title .': '. $instance->release_title .' ('. $instance->occurance_count .')');
+ }
+ else {
+ $list[] = $instance->release_title .' ('. $instance->occurance_count .')';
+ }
+ $previous_project = $instance->project_title;
+ }
+ $output[] = join(', ', $list);
+ print ''. t('Used in:') .''. theme('item_list', $output);
+
+ exit;
+}
diff --git l10n_community/welcome.inc l10n_community/welcome.inc
index 48c380b..639a837 100644
--- l10n_community/welcome.inc
+++ l10n_community/welcome.inc
@@ -114,7 +114,7 @@ function l10n_community_pick_go() {
'#value' => $language_code
);
}
-
+
if (count($language_list) > 1) {
$projects_title = user_access('browse translations') ? t('And/or pick a project') : t('Or pick a project');
}
diff --git l10n_remote/l10n_remote.module l10n_remote/l10n_remote.module
index 4c1c384..0e42aa2 100644
--- l10n_remote/l10n_remote.module
+++ l10n_remote/l10n_remote.module
@@ -141,7 +141,7 @@ function l10n_remote_xmlrpc_string_submit($langcode, $source, $translation, $uid
//watchdog('l10n_community', 'Language not allowed for remote submission.', NULL, WATCHDOG_WARNING);
return array('status' => FALSE, 'reason' => 'Language not accepted.');
}
-
+
// Check if the user has permission to submit strings in this language.
if (!(l10n_community_get_permission($langcode, $account) & L10N_PERM_SUGGEST)) {
//watchdog('l10n_community', 'Not allowed to submit translations in this language remotely.', NULL, WATCHDOG_WARNING);