code:
# Translation of StatusNet - ClientSideShorten to French (français)
# Exported from translatewiki.net
#
# Author: Peter17
# Author: Verdy p
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: French <https://translatewiki.net/wiki/Portal:fr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: fr\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten fait en sorte que le formulaire d’avis de l’interface "
"raccourcisse automatiquement les URL lorsqu’elles sont saisies, avant que "
"l’avis ne soit soumis."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "L’argument « text » doit être spécifié."


code:
# Translation of StatusNet - ClientSideShorten to Galician (galego)
# Exported from translatewiki.net
#
# Author: Toliño
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Galician <https://translatewiki.net/wiki/Portal:gl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten causa que o formulario de notas da interface acurte os "
"enderezos URL automaticamente á vez que se escriben e antes de enviar a nota."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Cómpre especificar o argumento \"text\"."

license: mit


code:
# Translation of StatusNet - ClientSideShorten to Hebrew (עברית)
# Exported from translatewiki.net
#
# Author: Amire80
# Author: YaronSh
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Hebrew <https://translatewiki.net/wiki/Portal:he>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: he\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"התוסף ClientSideShorten (קיצור מצד הלקוח) גורם לטופס הזנת העדכון בממשק הוובי "
"לקצר אוטומטית כתובות כאשר הן מוזנות ולפני שהעדכון נשלח."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "יש ציין את המשתנה \"text\"."


code:
# Translation of StatusNet - ClientSideShorten to Interlingua (interlingua)
# Exported from translatewiki.net
#
# Author: McDutchie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Interlingua <https://translatewiki.net/wiki/Portal:ia>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ia\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten causa que le formulario web pro entrar notas abbrevia "
"automaticamente le adresses URL a lor entrata, e ante le submission del nota."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Le parametro \"text\" debe esser specificate."


code:
# Translation of StatusNet - ClientSideShorten to Indonesian (Bahasa Indonesia)
# Exported from translatewiki.net
#
# Author: Farras
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Indonesian <https://translatewiki.net/wiki/Portal:id>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: id\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten menyebabkan bentuk pemberitahuan antarmuka web untuk "
"memendekkan URL secara otomatis ketika dimasukkan, dan sebelum pemberitahuan "
"dikirim."

#. TRANS: Client exception thrown when a text argument is not present.
#, fuzzy
msgid "\"text\" argument must be specified."
msgstr "Argumen 'text' harus disebutkan."


code:
# Translation of StatusNet - ClientSideShorten to Italian (italiano)
# Exported from translatewiki.net
#
# Author: Ximo17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten genera il form di notifica d'interfaccia web per "
"accorciare automaticamente gli URL non appena vengono inseriti, e prima che "
"la notifica venga inviata."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "è necessario specificare l'argomento del \"testo\"."


code:
# Translation of StatusNet - ClientSideShorten to Macedonian (македонски)
# Exported from translatewiki.net
#
# Author: Bjankuloski06
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Macedonian <https://translatewiki.net/wiki/Portal:mk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: mk\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"Со ClientSideShorten, образецот за забелешки во мрежниот посредник "
"автоматски ги скратува URL-адресите при самото нивно внесување, и пред да се "
"поднесе забелешката."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Мора да се назначи аргументот „text“."


code:
# Translation of StatusNet - ClientSideShorten to Norwegian Bokmål (norsk (bokmål))
# Exported from translatewiki.net
#
# Author: Nghtwlkr
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Norwegian Bokmål <https://translatewiki.net/wiki/Portal:nb>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nb\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten fører til at nettgrensesnittets notisskjema automatisk "
"forkorter URL-er mens de skrives inn, og før notisen sendes inn."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "«text»-argumentet må spesifiseres."


code:
# Translation of StatusNet - ClientSideShorten to Dutch (Nederlands)
# Exported from translatewiki.net
#
# Author: Siebrand
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:03+0000\n"
"Language-Team: Dutch <https://translatewiki.net/wiki/Portal:nl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten zorgt dat URLs die worden ingegeven in het "
"mededelingenformulier automatisch worden ingekort tijdens het invoeren en "
"voordat de mededeling wordt opgeslagen."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Het argument 'text' moet aangegeven worden."


code:
# Translation of StatusNet - ClientSideShorten to Russian (русский)
# Exported from translatewiki.net
#
# Author: Adata80
# Author: Lockal
# Author: Александр Сигачёв
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: Russian <https://translatewiki.net/wiki/Portal:ru>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"Плагин ClientSideShorten производит автоматически сокращать URL-адреса при "
"вводе в форму веб-интерфейса перед отправкой записи."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "\"text\" параметр должен быть указан."


code:
# Translation of StatusNet - ClientSideShorten to Tagalog (Tagalog)
# Exported from translatewiki.net
#
# Author: AnakngAraw
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: Tagalog <https://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"Ang ClientSideShorten ay nakapagsasanhi sa pormularyo ng pabatid ng ugnayang-"
"mukha ng web na kusang paiksiin ang mga URL habang ipinapasok sila, at bago "
"ipasa ang pabatid."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Dapat tukuyin ang pangangatwiran ng \"teksto\"."


code:
# Translation of StatusNet - ClientSideShorten to Ukrainian (українська)
# Exported from translatewiki.net
#
# Author: Boogie
# Author: Lockal
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: Ukrainian <https://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"ClientSideShorten зазначає, чи будуть автоматично скорочуватись URL-адреси "
"при використанні веб-інтерфейсу для надсилання допису на сайт до того, як "
"допис буде надіслано."

#. TRANS: Client exception thrown when a text argument is not present.
msgid "\"text\" argument must be specified."
msgstr "Аргумент «text» має бути зазначений."


code:
# Translation of StatusNet - ClientSideShorten to Simplified Chinese (中文（简体）)
# Exported from translatewiki.net
#
# Author: Chenxiaoqino
# Author: ZhengYiFeng
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - ClientSideShorten\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: Simplified Chinese <https://translatewiki.net/wiki/Portal:zh-"
"hans>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:52+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-clientsideshorten\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description.
msgid ""
"ClientSideShorten causes the web interface's notice form to automatically "
"shorten URLs as they entered, and before the notice is submitted."
msgstr ""
"客户端短网址（ClientSideShorten ）将在消息发布前自动在网页界面下缩短输入的网"
"址。"

#. TRANS: Client exception thrown when a text argument is not present.
#, fuzzy
msgid "\"text\" argument must be specified."
msgstr "需要定义'text' 变量。"


code:
//wrap everything in a self-executing anonymous function to avoid conflicts
(function(){

    // smart(x) from Paul Irish
    // http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/

    (function($,sr){

        // debouncing function from John Hann
        // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
        var debounce = function (func, threshold, execAsap) {
            var timeout;

            return function debounced () {
                var obj = this, args = arguments;
                function delayed () {
                    if (!execAsap)
                        func.apply(obj, args);
                        timeout = null;
                };

                if (timeout)
                    clearTimeout(timeout);
                else if (execAsap)
                    func.apply(obj, args);

                timeout = setTimeout(delayed, threshold || 100);
            };
        }
        jQuery.fn[sr] = function(fn){  return fn ? this.bind('keypress', debounce(fn, 1000)) : this.trigger(sr); };

    })(jQuery,'smartkeypress');

    function longestWordInString(string)
    {
        var words = string.split(/\s/);
        var longestWord = 0;
        for(var i=0;i<words.length;i++)
            if(words[i].length > longestWord) longestWord = words[i].length;
        return longestWord;
    }

    function shorten()
    {
        var $noticeDataText = $('#'+SN.C.S.NoticeDataText);
        var noticeText = $noticeDataText.val();

        if(noticeText.length > maxNoticeLength || longestWordInString(noticeText) > maxUrlLength) {
            var original = $noticeDataText.val();
            shortenAjax = $.ajax({
                url: $('address .url')[0].href+'/plugins/ClientSideShorten/shorten',
                data: { text: $noticeDataText.val() },
                dataType: 'text',
                success: function(data) {
                    if(original == $noticeDataText.val()) {
                        $noticeDataText.val(data).keyup();
                    }
                }
            });
        }
    }

    $(document).ready(function(){
        $noticeDataText = $('#'+SN.C.S.NoticeDataText);
        $noticeDataText.smartkeypress(function(e){
            //if(typeof(shortenAjax) !== 'undefined') shortenAjax.abort();
            if(e.charCode == '32') {
                shorten();
            }
        });
        $noticeDataText.bind('paste', function() {
            //if(typeof(shortenAjax) !== 'undefined') shortenAjax.abort();
            setTimeout(shorten,1);
        });
    });

})();


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Plugin to do "real time" updates using Comet/Bayeux
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Plugin
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('GNUSOCIAL') && !defined('STATUSNET')) {
    exit(1);
}

require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php';

/**
 * Plugin to do realtime updates using Comet
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class CometPlugin extends RealtimePlugin
{
    public $server   = null;
    public $username = null;
    public $password = null;
    public $prefix   = null;
    protected $bay   = null;

    function __construct($server=null, $username=null, $password=null, $prefix=null)
    {
        $this->server   = $server;
        $this->username = $username;
        $this->password = $password;
        $this->prefix   = $prefix;

        parent::__construct();
    }

    function _getScripts()
    {
        $scripts = parent::_getScripts();

        $ours = array('js/jquery.comet.js', 'js/cometupdate.js');

        foreach ($ours as $script) {
            $scripts[] = $this->path($script);
        }

        return $scripts;
    }

    function _updateInitialize($timeline, $user_id)
    {
        $script = parent::_updateInitialize($timeline, $user_id);
        return $script." CometUpdate.init(\"$this->server\", \"$timeline\", $user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->deleteurl\");";
    }

    function _connect()
    {
        require_once INSTALLDIR.'/plugins/Comet/extlib/Bayeux/Bayeux.class.php';
        // Bayeux? Comet? Huh? These terms confuse me
        $this->bay = new Bayeux($this->server, $this->user, $this->password);
    }

    function _publish($timeline, $json)
    {
        $this->bay->publish($timeline, $json);
    }

    function _disconnect()
    {
        unset($this->bay);
    }

    function _pathToChannel($path)
    {
        if (!empty($this->prefix)) {
            array_unshift($path, $this->prefix);
        }
        return '/' . implode('/', $path);
    }

    function onPluginVersion(&$versions)
    {
        $versions[] = array('name' => 'Comet',
                            'version' => GNUSOCIAL_VERSION,
                            'author' => 'Evan Prodromou',
                            'homepage' => 'http://status.net/wiki/Plugin:Comet',
                            'rawdescription' =>
                            // TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
                            // TRANS: and Comet is a web application model.
                            _m('Plugin to make updates using Comet and Bayeux.'));
        return true;
    }
}


code:
This is a plugin to automatically load notices in the browser no
matter who creates them -- the kind of thing we see with
search.twitter.com, rejaw.com, or FriendFeed's "real time" news.

NOTE: this is an insecure version; don't roll it out on a production
server.

It requires a cometd server. I've only had the cometd-java server work
correctly; something's wiggy with the Twisted-based server. See here
for help setting up a comet server:

    http://cometd.org/

After you have a cometd server installed, just add this code to your
config.php:

    require_once(INSTALLDIR.'/plugins/Comet/CometPlugin.php');
    $cp = new CometPlugin('http://example.com:8080/cometd/');

Change 'example.com:8080' to the name and port of the server you
installed cometd on.

TODO:

* Needs to be tested with Ajax submission. Probably messes everything
  up.
* Add more timelines: personal inbox and tags would be great.
* Add security. In particular, only let the PHP code publish notices
  to the cometd server. Currently, it doesn't try to authenticate.

path: plugins/Comet/README


code:
<?php
/*
 * Phomet: a php comet client
 *
 * Copyright (C) 2008 Morgan 'ARR!' Allen <morganrallen@gmail.com> http://morglog.alleycatracing.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

class Bayeux
{
    private $oCurl = '';
    private $nNextId = 0;

    private $sUser = '';
    private $sPassword = '';

    public $sUrl = '';

    function __construct($sUrl, $sUser='', $sPassword='')
    {
        $this->sUrl = $sUrl;

        $this->oCurl = curl_init();

        $aHeaders = array();
        $aHeaders[] = 'Connection: Keep-Alive';

        curl_setopt($this->oCurl, CURLOPT_URL, $sUrl);
        curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders);
        curl_setopt($this->oCurl, CURLOPT_HEADER, 0);
        curl_setopt($this->oCurl, CURLOPT_POST, 1);
        curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER,1);

        if (!is_null($sUser) && mb_strlen($sUser) > 0) {
            curl_setopt($this->oCurl, CURLOPT_USERPWD,"$sUser:$sPassword");
        }

        $this->handShake();
    }

    function __destruct()
    {
        $this->disconnect();
    }

    function handShake()
    {
        $msgHandshake = array();
        $msgHandshake['channel'] = '/meta/handshake';
        $msgHandshake['version'] = "1.0";
        $msgHandshake['minimumVersion'] = "0.9";
        $msgHandshake['supportedConnectionTypes'] = array('long-polling');
        $msgHandshake['id'] = $this->nNextId++;

        curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));

        $data = curl_exec($this->oCurl);

        if(curl_errno($this->oCurl))
          die("Error: " . curl_error($this->oCurl));

        $oReturn = json_decode($data);

        if (is_array($oReturn)) {
            $oReturn = $oReturn[0];
        }

        $bSuccessful = ($oReturn->successful) ? true : false;

        if($bSuccessful)
        {
            $this->clientId = $oReturn->clientId;

            $this->connect();
        }
    }

    public function connect()
    {
        $aMsg['channel'] = '/meta/connect';
        $aMsg['id'] = $this->nNextId++;
        $aMsg['clientId'] = $this->clientId;
        $aMsg['connectionType'] = 'long-polling';

        curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));

        $data = curl_exec($this->oCurl);
    }

    function disconnect()
    {
        $msgHandshake = array();
        $msgHandshake['channel'] = '/meta/disconnect';
        $msgHandshake['id'] = $this->nNextId++;
        $msgHandshake['clientId'] = $this->clientId;

        curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));

        curl_exec($this->oCurl);
    }

    public function publish($sChannel, $oData)
    {
        if(!$sChannel || !$oData)
          return;

        $aMsg = array();

        $aMsg['channel'] = $sChannel;
        $aMsg['id'] = $this->nNextId++;
        $aMsg['data'] = $oData;
        $aMsg['clientId'] = $this->clientId;

        curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));

        $data = curl_exec($this->oCurl);
//        var_dump($data);
    }
}


code:
// update the local timeline from a Comet server
var CometUpdate = function()
{
     var _server;
     var _timeline;
     var _userid;
     var _replyurl;
     var _favorurl;
     var _deleteurl;
     var _cometd;

     return {
          init: function(server, timeline, userid, replyurl, favorurl, deleteurl)
          {
               _cometd = $.cometd; // Uses the default Comet object
               _cometd.init(server);
               _server = server;
               _timeline = timeline;
               _userid = userid;
               _favorurl = favorurl;
               _replyurl = replyurl;
               _deleteurl = deleteurl;
               _cometd.subscribe(timeline, function(message) { RealtimeUpdate.receive(message.data) });
               $(window).unload(function() { _cometd.disconnect(); } );
          }
     }
}();


code:
/**
 * Copyright 2008 Mort Bay Consulting Pty. Ltd.
 * Dual licensed under the Apache License 2.0 and the MIT license.
 * ----------------------------------------------------------------------------
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http: *www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ----------------------------------------------------------------------------
 * Licensed under the MIT license;
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ----------------------------------------------------------------------------
 * $Revision$ $Date$
 */
(function($)
{
    /**
     * The constructor for a Comet object.
     * There is a default Comet instance already created at the variable $.cometd,
     * and hence that can be used to start a comet conversation with a server.
     * In the rare case a page needs more than one comet conversation, a new instance can be
     * created via:
     *

     * var url2 = ...;
     * var cometd2 = new $.Cometd();
     * cometd2.init(url2);
     *

     */
    $.Cometd = function(name)
    {
        var _name = name || 'default';
        var _logPriorities = { debug: 1, info: 2, warn: 3, error: 4 };
        var _logLevel = 'info';
        var _url;
        var _xd = false;
        var _transport;
        var _status = 'disconnected';
        var _messageId = 0;
        var _clientId = null;
        var _batch = 0;
        var _messageQueue = [];
        var _listeners = {};
        var _backoff = 0;
        var _backoffIncrement = 1000;
        var _maxBackoff = 60000;
        var _scheduledSend = null;
        var _extensions = [];
        var _advice = {};
        var _handshakeProps;

        /**
         * Returns the name assigned to this Comet object, or the string 'default'
         * if no name has been explicitely passed as parameter to the constructor.
         */
        this.getName = function()
        {
            return _name;
        };

        /**
         * Configures the initial comet communication with the comet server.
         * @param cometURL the URL of the comet server
         */
        this.configure = function(cometURL)
        {
            _configure(cometURL);
        };

        function _configure(cometURL)
        {
            _url = cometURL;
            _debug('Initializing comet with url: {}', _url);

            // Check immediately if we're cross domain
            // If cross domain, the handshake must not send the long polling transport type
            var urlParts = /(^https?:)?(\/\/(([^:\/\?#]+)(:(\d+))?))?([^\?#]*)/.exec(cometURL);
            if (urlParts[3]) _xd = urlParts[3] != location.host;

            // Temporary setup a transport to send the initial handshake
            // The transport may be changed as a result of handshake
            if (_xd)
                _transport = newCallbackPollingTransport();
            else
                _transport = newLongPollingTransport();
            _debug('Initial transport is {}', _transport.getType());
        };

        /**
         * Configures and establishes the comet communication with the comet server
         * via a handshake and a subsequent connect.
         * @param cometURL the URL of the comet server
         * @param handshakeProps an object to be merged with the handshake message
         * @see #configure(cometURL)
         * @see #handshake(handshakeProps)
         */
        this.init = function(cometURL, handshakeProps)
        {
            _configure(cometURL);
            _handshake(handshakeProps);
        };

        /**
         * Establishes the comet communication with the comet server
         * via a handshake and a subsequent connect.
         * @param handshakeProps an object to be merged with the handshake message
         */
        this.handshake = function(handshakeProps)
        {
            _handshake(handshakeProps);
        };

        /**
         * Disconnects from the comet server.
         * @param disconnectProps an object to be merged with the disconnect message
         */
        this.disconnect = function(disconnectProps)
        {
            var bayeuxMessage = {
                channel: '/meta/disconnect'
            };
            var message = $.extend({}, disconnectProps, bayeuxMessage);
            // Deliver immediately
            // The handshake and connect mechanism make use of startBatch(), and in case
            // of a failed handshake the disconnect would not be delivered if using _send().
            _setStatus('disconnecting');
            _deliver([message], false);
        };

        /**
         * Marks the start of a batch of application messages to be sent to the server
         * in a single request, obtaining a single response containing (possibly) many
         * application reply messages.
         * Messages are held in a queue and not sent until {@link #endBatch()} is called.
         * If startBatch() is called multiple times, then an equal number of endBatch()
         * calls must be made to close and send the batch of messages.
         * @see #endBatch()
         */
        this.startBatch = function()
        {
            _startBatch();
        };

        /**
         * Marks the end of a batch of application messages to be sent to the server
         * in a single request.
         * @see #startBatch()
         */
        this.endBatch = function()
        {
            _endBatch(true);
        };

        /**
         * Subscribes to the given channel, performing the given callback in the given scope
         * when a message for the channel arrives.
         * @param channel the channel to subscribe to
         * @param scope the scope of the callback
         * @param callback the callback to call when a message is delivered to the channel
         * @param subscribeProps an object to be merged with the subscribe message
         * @return the subscription handle to be passed to {@link #unsubscribe(object)}
         */
        this.subscribe = function(channel, scope, callback, subscribeProps)
        {
            var subscription = this.addListener(channel, scope, callback);

            // Send the subscription message after the subscription registration to avoid
            // races where the server would deliver a message to the subscribers, but here
            // on the client the subscription has not been added yet to the data structures
            var bayeuxMessage = {
                channel: '/meta/subscribe',
                subscription: channel
            };
            var message = $.extend({}, subscribeProps, bayeuxMessage);
            _send(message);

            return subscription;
        };

        /**
         * Unsubscribes the subscription obtained with a call to {@link #subscribe(string, object, function)}.
         * @param subscription the subscription to unsubscribe.
         */
        this.unsubscribe = function(subscription, unsubscribeProps)
        {
            // Remove the local listener before sending the message
            // This ensures that if the server fails, this client does not get notifications
            this.removeListener(subscription);
            var bayeuxMessage = {
                channel: '/meta/unsubscribe',
                subscription: subscription[0]
            };
            var message = $.extend({}, unsubscribeProps, bayeuxMessage);
            _send(message);
        };

        /**
         * Publishes a message on the given channel, containing the given content.
         * @param channel the channel to publish the message to
         * @param content the content of the message
         * @param publishProps an object to be merged with the publish message
         */
        this.publish = function(channel, content, publishProps)
        {
            var bayeuxMessage = {
                channel: channel,
                data: content
            };
            var message = $.extend({}, publishProps, bayeuxMessage);
            _send(message);
        };

        /**
         * Adds a listener for bayeux messages, performing the given callback in the given scope
         * when a message for the given channel arrives.
         * @param channel the channel the listener is interested to
         * @param scope the scope of the callback
         * @param callback the callback to call when a message is delivered to the channel
         * @returns the subscription handle to be passed to {@link #removeListener(object)}
         * @see #removeListener(object)
         */
        this.addListener = function(channel, scope, callback)
        {
            // The data structure is a map<channel, subscription[]>, where each subscription
            // holds the callback to be called and its scope.

            // Normalize arguments
            if (!callback)
            {
                callback = scope;
                scope = undefined;
            }

            var subscription = {
                scope: scope,
                callback: callback
            };

            var subscriptions = _listeners[channel];
            if (!subscriptions)
            {
                subscriptions = [];
                _listeners[channel] = subscriptions;
            }
            // Pushing onto an array appends at the end and returns the id associated with the element increased by 1.
            // Note that if:
            // a.push('a'); var hb=a.push('b'); delete a[hb-1]; var hc=a.push('c');
            // then:
            // hc==3, a.join()=='a',,'c', a.length==3
            var subscriptionIndex = subscriptions.push(subscription) - 1;
            _debug('Added listener: channel \'{}\', callback \'{}\', index {}', channel, callback.name, subscriptionIndex);

            // The subscription to allow removal of the listener is made of the channel and the index
            return [channel, subscriptionIndex];
        };

        /**
         * Removes the subscription obtained with a call to {@link #addListener(string, object, function)}.
         * @param subscription the subscription to unsubscribe.
         */
        this.removeListener = function(subscription)
        {
            var subscriptions = _listeners[subscription[0]];
            if (subscriptions)
            {
                delete subscriptions[subscription[1]];
                _debug('Removed listener: channel \'{}\', index {}', subscription[0], subscription[1]);
            }
        };

        /**
         * Removes all listeners registered with {@link #addListener(channel, scope, callback)} or
         * {@link #subscribe(channel, scope, callback)}.
         */
        this.clearListeners = function()
        {
            _listeners = {};
        };

        /**
         * Returns a string representing the status of the bayeux communication with the comet server.
         */
        this.getStatus = function()
        {
            return _status;
        };

        /**
         * Sets the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
         * Default value is 1 second, which means if there is a persistent failure the retries will happen
         * after 1 second, then after 2 seconds, then after 3 seconds, etc. So for example with 15 seconds of
         * elapsed time, there will be 5 retries (at 1, 3, 6, 10 and 15 seconds elapsed).
         * @param period the backoff period to set
         * @see #getBackoffIncrement()
         */
        this.setBackoffIncrement = function(period)
        {
            _backoffIncrement = period;
        };

        /**
         * Returns the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
         * @see #setBackoffIncrement(period)
         */
        this.getBackoffIncrement = function()
        {
            return _backoffIncrement;
        };

        /**
         * Returns the backoff period to wait before retrying an unsuccessful or failed message.
         */
        this.getBackoffPeriod = function()
        {
            return _backoff;
        };

        /**
         * Sets the log level for console logging.
         * Valid values are the strings 'error', 'warn', 'info' and 'debug', from
         * less verbose to more verbose.
         * @param level the log level string
         */
        this.setLogLevel = function(level)
        {
            _logLevel = level;
        };

        /**
         * Registers an extension whose callbacks are called for every incoming message
         * (that comes from the server to this client implementation) and for every
         * outgoing message (that originates from this client implementation for the
         * server).
         * The format of the extension object is the following:
         *

         * {
         *     incoming: function(message) { ... },
         *     outgoing: function(message) { ... }
         * }
         * Both properties are optional, but if they are present they will be called
         * respectively for each incoming message and for each outgoing message.
         *

         * @param name the name of the extension
         * @param extension the extension to register
         * @return true if the extension was registered, false otherwise
         * @see #unregisterExtension(name)
         */
        this.registerExtension = function(name, extension)
        {
            var existing = false;
            for (var i = 0; i < _extensions.length; ++i)
            {
                var existingExtension = _extensions[i];
                if (existingExtension.name == name)
                {
                    existing = true;
                    return false;
                }
            }
            if (!existing)
            {
                _extensions.push({
                    name: name,
                    extension: extension
                });
                _debug('Registered extension \'{}\'', name);
                return true;
            }
            else
            {
                _info('Could not register extension with name \'{}\': another extension with the same name already exists');
                return false;
            }
        };

        /**
         * Unregister an extension previously registered with
         * {@link #registerExtension(name, extension)}.
         * @param name the name of the extension to unregister.
         * @return true if the extension was unregistered, false otherwise
         */
        this.unregisterExtension = function(name)
        {
            var unregistered = false;
            $.each(_extensions, function(index, extension)
            {
                if (extension.name == name)
                {
                    _extensions.splice(index, 1);
                    unregistered = true;
                    _debug('Unregistered extension \'{}\'', name);
                    return false;
                }
            });
            return unregistered;
        };

        /**
         * Starts a the batch of messages to be sent in a single request.
         * @see _endBatch(deliverMessages)
         */
        function _startBatch()
        {
            ++_batch;
        };

        /**
         * Ends the batch of messages to be sent in a single request,
         * optionally delivering messages present in the message queue depending
         * on the given argument.
         * @param deliverMessages whether to deliver the messages in the queue or not
         * @see _startBatch()
         */
        function _endBatch(deliverMessages)
        {
            --_batch;
            if (_batch < 0) _batch = 0;
            if (deliverMessages && _batch == 0 && !_isDisconnected())
            {
                var messages = _messageQueue;
                _messageQueue = [];
                if (messages.length > 0) _deliver(messages, false);
            }
        };

        function _nextMessageId()
        {
            return ++_messageId;
        };

        /**
         * Converts the given response into an array of bayeux messages
         * @param response the response to convert
         * @return an array of bayeux messages obtained by converting the response
         */
        function _convertToMessages(response)
        {
            if (response === undefined) return [];
            if (response instanceof Array) return response;
            if (response instanceof String || typeof response == 'string') return eval('(' + response + ')');
            if (response instanceof Object) return [response];
            throw 'Conversion Error ' + response + ', typeof ' + (typeof response);
        };

        function _setStatus(newStatus)
        {
            _debug('{} -> {}', _status, newStatus);
            _status = newStatus;
        };

        function _isDisconnected()
        {
            return _status == 'disconnecting' || _status == 'disconnected';
        };

        /**
         * Sends the initial handshake message
         */
        function _handshake(handshakeProps)
        {
            _debug('Starting handshake');
            _clientId = null;

            // Start a batch.
            // This is needed because handshake and connect are async.
            // It may happen that the application calls init() then subscribe()
            // and the subscribe message is sent before the connect message, if
            // the subscribe message is not held until the connect message is sent.
            // So here we start a batch to hold temporarly any message until
            // the connection is fully established.
            _batch = 0;
            _startBatch();

            // Save the original properties provided by the user
            // Deep copy to avoid the user to be able to change them later
            _handshakeProps = $.extend(true, {}, handshakeProps);

            var bayeuxMessage = {
                version: '1.0',
                minimumVersion: '0.9',
                channel: '/meta/handshake',
                supportedConnectionTypes: _xd ? ['callback-polling'] : ['long-polling', 'callback-polling']
            };
            // Do not allow the user to mess with the required properties,
            // so merge first the user properties and *then* the bayeux message
            var message = $.extend({}, handshakeProps, bayeuxMessage);

            // We started a batch to hold the application messages,
            // so here we must bypass it and deliver immediately.
            _setStatus('handshaking');
            _deliver([message], false);
        };

        function _findTransport(handshakeResponse)
        {
            var transportTypes = handshakeResponse.supportedConnectionTypes;
            if (_xd)
            {
                // If we are cross domain, check if the server supports it, that's the only option
                if ($.inArray('callback-polling', transportTypes) >= 0) return _transport;
            }
            else
            {
                // Check if we can keep long-polling
                if ($.inArray('long-polling', transportTypes) >= 0) return _transport;

                // The server does not support long-polling
                if ($.inArray('callback-polling', transportTypes) >= 0) return newCallbackPollingTransport();
            }
            return null;
        };

        function _delayedHandshake()
        {
            _setStatus('handshaking');
            _delayedSend(function()
            {
                _handshake(_handshakeProps);
            });
        };

        function _delayedConnect()
        {
            _setStatus('connecting');
            _delayedSend(function()
            {
                _connect();
            });
        };

        function _delayedSend(operation)
        {
            _cancelDelayedSend();
            var delay = _backoff;
            _debug("Delayed send: backoff {}, interval {}", _backoff, _advice.interval);
            if (_advice.interval && _advice.interval > 0)
                delay += _advice.interval;
            _scheduledSend = _setTimeout(operation, delay);
        };

        function _cancelDelayedSend()
        {
            if (_scheduledSend !== null) clearTimeout(_scheduledSend);
            _scheduledSend = null;
        };

        function _setTimeout(funktion, delay)
        {
            return setTimeout(function()
            {
                try
                {
                    funktion();
                }
                catch (x)
                {
                    _debug('Exception during scheduled execution of function \'{}\': {}', funktion.name, x);
                }
            }, delay);
        };

        /**
         * Sends the connect message
         */
        function _connect()
        {
            _debug('Starting connect');
            var message = {
                channel: '/meta/connect',
                connectionType: _transport.getType()
            };
            _setStatus('connecting');
            _deliver([message], true);
            _setStatus('connected');
        };

        function _send(message)
        {
            if (_batch > 0)
                _messageQueue.push(message);
            else
                _deliver([message], false);
        };

        /**
         * Delivers the messages to the comet server
         * @param messages the array of messages to send
         */
        function _deliver(messages, comet)
        {
            // We must be sure that the messages have a clientId.
            // This is not guaranteed since the handshake may take time to return
            // (and hence the clientId is not known yet) and the application
            // may create other messages.
            $.each(messages, function(index, message)
            {
                message['id'] = _nextMessageId();
                if (_clientId) message['clientId'] = _clientId;
                messages[index] = _applyOutgoingExtensions(message);
            });

            var self = this;
            var envelope = {
                url: _url,
                messages: messages,
                onSuccess: function(request, response)
                {
                    try
                    {
                        _handleSuccess.call(self, request, response, comet);
                    }
                    catch (x)
                    {
                        _debug('Exception during execution of success callback: {}', x);
                    }
                },
                onFailure: function(request, reason, exception)
                {
                    try
                    {
                        _handleFailure.call(self, request, messages, reason, exception, comet);
                    }
                    catch (x)
                    {
                        _debug('Exception during execution of failure callback: {}', x);
                    }
                }
            };
            _debug('Sending request to {}, message(s): {}', envelope.url, JSON.stringify(envelope.messages));
            _transport.send(envelope, comet);
        };

        function _applyIncomingExtensions(message)
        {
            for (var i = 0; i < _extensions.length; ++i)
            {
                var extension = _extensions[i];
                var callback = extension.extension.incoming;
                if (callback && typeof callback === 'function')
                {
                    _debug('Calling incoming extension \'{}\', callback \'{}\'', extension.name, callback.name);
                    message = _applyExtension(extension.name, callback, message) || message;
                }
            }
            return message;
        };

        function _applyOutgoingExtensions(message)
        {
            for (var i = 0; i < _extensions.length; ++i)
            {
                var extension = _extensions[i];
                var callback = extension.extension.outgoing;
                if (callback && typeof callback === 'function')
                {
                    _debug('Calling outgoing extension \'{}\', callback \'{}\'', extension.name, callback.name);
                    message = _applyExtension(extension.name, callback, message) || message;
                }
            }
            return message;
        };

        function _applyExtension(name, callback, message)
        {
            try
            {
                return callback(message);
            }
            catch (x)
            {
                _debug('Exception during execution of extension \'{}\': {}', name, x);
                return message;
            }
        };

        function _handleSuccess(request, response, comet)
        {
            var messages = _convertToMessages(response);
            _debug('Received response {}', JSON.stringify(messages));

            // Signal the transport it can deliver other queued requests
            _transport.complete(request, true, comet);

            for (var i = 0; i < messages.length; ++i)
            {
                var message = messages[i];
                message = _applyIncomingExtensions(message);

                if (message.advice) _advice = message.advice;

                var channel = message.channel;
                switch (channel)
                {
                    case '/meta/handshake':
                        _handshakeSuccess(message);
                        break;
                    case '/meta/connect':
                        _connectSuccess(message);
                        break;
                    case '/meta/disconnect':
                        _disconnectSuccess(message);
                        break;
                    case '/meta/subscribe':
                        _subscribeSuccess(message);
                        break;
                    case '/meta/unsubscribe':
                        _unsubscribeSuccess(message);
                        break;
                    default:
                        _messageSuccess(message);
                        break;
                }
            }
        };

        function _handleFailure(request, messages, reason, exception, comet)
        {
            var xhr = request.xhr;
            _debug('Request failed, status: {}, reason: {}, exception: {}', xhr && xhr.status, reason, exception);

            // Signal the transport it can deliver other queued requests
            _transport.complete(request, false, comet);

            for (var i = 0; i < messages.length; ++i)
            {
                var message = messages[i];
                var channel = message.channel;
                switch (channel)
                {
                    case '/meta/handshake':
                        _handshakeFailure(xhr, message);
                        break;
                    case '/meta/connect':
                        _connectFailure(xhr, message);
                        break;
                    case '/meta/disconnect':
                        _disconnectFailure(xhr, message);
                        break;
                    case '/meta/subscribe':
                        _subscribeFailure(xhr, message);
                        break;
                    case '/meta/unsubscribe':
                        _unsubscribeFailure(xhr, message);
                        break;
                    default:
                        _messageFailure(xhr, message);
                        break;
                }
            }
        };

        function _handshakeSuccess(message)
        {
            if (message.successful)
            {
                _debug('Handshake successful');
                // Save clientId, figure out transport, then follow the advice to connect
                _clientId = message.clientId;

                var newTransport = _findTransport(message);
                if (newTransport === null)
                {
                    throw 'Could not agree on transport with server';
                }
                else
                {
                    if (_transport.getType() != newTransport.getType())
                    {
                        _debug('Changing transport from {} to {}', _transport.getType(), newTransport.getType());
                        _transport = newTransport;
                    }
                }

                // Notify the listeners
                // Here the new transport is in place, as well as the clientId, so
                // the listener can perform a publish() if it wants, and the listeners
                // are notified before the connect below.
                _notifyListeners('/meta/handshake', message);

                var action = _advice.reconnect ? _advice.reconnect : 'retry';
                switch (action)
                {
                    case 'retry':
                        _delayedConnect();
                        break;
                    default:
                        break;
                }
            }
            else
            {
                _debug('Handshake unsuccessful');

                var retry = !_isDisconnected() && _advice.reconnect != 'none';
                if (!retry) _setStatus('disconnected');

                _notifyListeners('/meta/handshake', message);
                _notifyListeners('/meta/unsuccessful', message);

                // Only try again if we haven't been disconnected and
                // the advice permits us to retry the handshake
                if (retry)
                {
                    _increaseBackoff();
                    _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
                    _delayedHandshake();
                }
            }
        };

        function _handshakeFailure(xhr, message)
        {
            _debug('Handshake failure');

            // Notify listeners
            var failureMessage = {
                successful: false,
                failure: true,
                channel: '/meta/handshake',
                request: message,
                xhr: xhr,
                advice: {
                    action: 'retry',
                    interval: _backoff
                }
            };

            var retry = !_isDisconnected() && _advice.reconnect != 'none';
            if (!retry) _setStatus('disconnected');

            _notifyListeners('/meta/handshake', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);

            // Only try again if we haven't been disconnected and the
            // advice permits us to try again
            if (retry)
            {
                _increaseBackoff();
                _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
                _delayedHandshake();
            }
        };

        function _connectSuccess(message)
        {
            var action = _isDisconnected() ? 'none' : (_advice.reconnect ? _advice.reconnect : 'retry');
            if (!_isDisconnected()) _setStatus(action == 'retry' ? 'connecting' : 'disconnecting');

            if (message.successful)
            {
                _debug('Connect successful');

                // End the batch and allow held messages from the application
                // to go to the server (see _handshake() where we start the batch).
                // The batch is ended before notifying the listeners, so that
                // listeners can batch other cometd operations
                _endBatch(true);

                // Notify the listeners after the status change but before the next connect
                _notifyListeners('/meta/connect', message);

                // Connect was successful.
                // Normally, the advice will say "reconnect: 'retry', interval: 0"
                // and the server will hold the request, so when a response returns
                // we immediately call the server again (long polling)
                switch (action)
                {
                    case 'retry':
                        _resetBackoff();
                        _delayedConnect();
                        break;
                    default:
                        _resetBackoff();
                        _setStatus('disconnected');
                        break;
                }
            }
            else
            {
                _debug('Connect unsuccessful');

                // Notify the listeners after the status change but before the next action
                _notifyListeners('/meta/connect', message);
                _notifyListeners('/meta/unsuccessful', message);

                // Connect was not successful.
                // This may happen when the server crashed, the current clientId
                // will be invalid, and the server will ask to handshake again
                switch (action)
                {
                    case 'retry':
                        _increaseBackoff();
                        _delayedConnect();
                        break;
                    case 'handshake':
                        // End the batch but do not deliver the messages until we connect successfully
                        _endBatch(false);
                        _resetBackoff();
                        _delayedHandshake();
                        break;
                    case 'none':
                        _resetBackoff();
                        _setStatus('disconnected');
                        break;
                }
            }
        };

        function _connectFailure(xhr, message)
        {
            _debug('Connect failure');

            // Notify listeners
            var failureMessage = {
                successful: false,
                failure: true,
                channel: '/meta/connect',
                request: message,
                xhr: xhr,
                advice: {
                    action: 'retry',
                    interval: _backoff
                }
            };
            _notifyListeners('/meta/connect', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);

            if (!_isDisconnected())
            {
                var action = _advice.reconnect ? _advice.reconnect : 'retry';
                switch (action)
                {
                    case 'retry':
                        _increaseBackoff();
                        _debug('Connect failure, backing off and retrying in {} ms', _backoff);
                        _delayedConnect();
                        break;
                    case 'handshake':
                        _resetBackoff();
                        _delayedHandshake();
                        break;
                    case 'none':
                        _resetBackoff();
                        break;
                    default:
                        _debug('Unrecognized reconnect value: {}', action);
                        break;
                }
            }
        };

        function _disconnectSuccess(message)
        {
            if (message.successful)
            {
                _debug('Disconnect successful');
                _disconnect(false);
                _notifyListeners('/meta/disconnect', message);
            }
            else
            {
                _debug('Disconnect unsuccessful');
                _disconnect(true);
                _notifyListeners('/meta/disconnect', message);
                _notifyListeners('/meta/usuccessful', message);
            }
        };

        function _disconnect(abort)
        {
            _cancelDelayedSend();
            if (abort) _transport.abort();
            _clientId = null;
            _setStatus('disconnected');
            _batch = 0;
            _messageQueue = [];
            _resetBackoff();
        };

        function _disconnectFailure(xhr, message)
        {
            _debug('Disconnect failure');
            _disconnect(true);

            var failureMessage = {
                successful: false,
                failure: true,
                channel: '/meta/disconnect',
                request: message,
                xhr: xhr,
                advice: {
                    action: 'none',
                    interval: 0
                }
            };
            _notifyListeners('/meta/disconnect', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);
        };

        function _subscribeSuccess(message)
        {
            if (message.successful)
            {
                _debug('Subscribe successful');
                _notifyListeners('/meta/subscribe', message);
            }
            else
            {
                _debug('Subscribe unsuccessful');
                _notifyListeners('/meta/subscribe', message);
                _notifyListeners('/meta/unsuccessful', message);
            }
        };

        function _subscribeFailure(xhr, message)
        {
            _debug('Subscribe failure');

            var failureMessage = {
                successful: false,
                failure: true,
                channel: '/meta/subscribe',
                request: message,
                xhr: xhr,
                advice: {
                    action: 'none',
                    interval: 0
                }
            };
            _notifyListeners('/meta/subscribe', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);
        };

        function _unsubscribeSuccess(message)
        {
            if (message.successful)
            {
                _debug('Unsubscribe successful');
                _notifyListeners('/meta/unsubscribe', message);
            }
            else
            {
                _debug('Unsubscribe unsuccessful');
                _notifyListeners('/meta/unsubscribe', message);
                _notifyListeners('/meta/unsuccessful', message);
            }
        };

        function _unsubscribeFailure(xhr, message)
        {
            _debug('Unsubscribe failure');

            var failureMessage = {
                successful: false,
                failure: true,
                channel: '/meta/unsubscribe',
                request: message,
                xhr: xhr,
                advice: {
                    action: 'none',
                    interval: 0
                }
            };
            _notifyListeners('/meta/unsubscribe', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);
        };

        function _messageSuccess(message)
        {
            if (message.successful === undefined)
            {
                if (message.data)
                {
                    // It is a plain message, and not a bayeux meta message
                    _notifyListeners(message.channel, message);
                }
                else
                {
                    _debug('Unknown message {}', JSON.stringify(message));
                }
            }
            else
            {
                if (message.successful)
                {
                    _debug('Publish successful');
                    _notifyListeners('/meta/publish', message);
                }
                else
                {
                    _debug('Publish unsuccessful');
                    _notifyListeners('/meta/publish', message);
                    _notifyListeners('/meta/unsuccessful', message);
                }
            }
        };

        function _messageFailure(xhr, message)
        {
            _debug('Publish failure');

            var failureMessage = {
                successful: false,
                failure: true,
                channel: message.channel,
                request: message,
                xhr: xhr,
                advice: {
                    action: 'none',
                    interval: 0
                }
            };
            _notifyListeners('/meta/publish', failureMessage);
            _notifyListeners('/meta/unsuccessful', failureMessage);
        };

        function _notifyListeners(channel, message)
        {
            // Notify direct listeners
            _notify(channel, message);

            // Notify the globbing listeners
            var channelParts = channel.split("/");
            var last = channelParts.length - 1;
            for (var i = last; i > 0; --i)
            {
                var channelPart = channelParts.slice(0, i).join('/') + '/*';
                // We don't want to notify /foo/* if the channel is /foo/bar/baz,
                // so we stop at the first non recursive globbing
                if (i == last) _notify(channelPart, message);
                // Add the recursive globber and notify
                channelPart += '*';
                _notify(channelPart, message);
            }
        };

        function _notify(channel, message)
        {
            var subscriptions = _listeners[channel];
            if (subscriptions && subscriptions.length > 0)
            {
                for (var i = 0; i < subscriptions.length; ++i)
                {
                    var subscription = subscriptions[i];
                    // Subscriptions may come and go, so the array may have 'holes'
                    if (subscription)
                    {
                        try
                        {
                            _debug('Notifying subscription: channel \'{}\', callback \'{}\'', channel, subscription.callback.name);
                            subscription.callback.call(subscription.scope, message);
                        }
                        catch (x)
                        {
                            // Ignore exceptions from callbacks
                            _warn('Exception during execution of callback \'{}\' on channel \'{}\' for message {}, exception: {}', subscription.callback.name, channel, JSON.stringify(message), x);
                        }
                    }
                }
            }
        };

        function _resetBackoff()
        {
            _backoff = 0;
        };

        function _increaseBackoff()
        {
            if (_backoff < _maxBackoff) _backoff += _backoffIncrement;
        };

        var _error = this._error = function(text, args)
        {
            _log('error', _format.apply(this, arguments));
        };

        var _warn = this._warn = function(text, args)
        {
            _log('warn', _format.apply(this, arguments));
        };

        var _info = this._info = function(text, args)
        {
            _log('info', _format.apply(this, arguments));
        };

        var _debug = this._debug = function(text, args)
        {
            _log('debug', _format.apply(this, arguments));
        };

        function _log(level, text)
        {
            var priority = _logPriorities[level];
            var configPriority = _logPriorities[_logLevel];
            if (!configPriority) configPriority = _logPriorities['info'];
            if (priority >= configPriority)
            {
                if (window.console) window.console.log(text);
            }
        };

        function _format(text)
        {
            var braces = /\{\}/g;
            var result = '';
            var start = 0;
            var count = 0;
            while (braces.test(text))
            {
                result += text.substr(start, braces.lastIndex - start - 2);
                var arg = arguments[++count];
                result += arg !== undefined ? arg : '{}';
                start = braces.lastIndex;
            }
            result += text.substr(start, text.length - start);
            return result;
        };

        function newLongPollingTransport()
        {
            return $.extend({}, new Transport('long-polling'), new LongPollingTransport());
        };

        function newCallbackPollingTransport()
        {
            return $.extend({}, new Transport('callback-polling'), new CallbackPollingTransport());
        };

        /**
         * Base object with the common functionality for transports.
         * The key responsibility is to allow at most 2 outstanding requests to the server,
         * to avoid that requests are sent behind a long poll.
         * To achieve this, we have one reserved request for the long poll, and all other
         * requests are serialized one after the other.
         */
        var Transport = function(type)
        {
            var _maxRequests = 2;
            var _requestIds = 0;
            var _cometRequest = null;
            var _requests = [];
            var _packets = [];

            this.getType = function()
            {
                return type;
            };

            this.send = function(packet, comet)
            {
                if (comet)
                    _cometSend(this, packet);
                else
                    _send(this, packet);
            };

            function _cometSend(self, packet)
            {
                if (_cometRequest !== null) throw 'Concurrent comet requests not allowed, request ' + _cometRequest.id + ' not yet completed';

                var requestId = ++_requestIds;
                _debug('Beginning comet request {}', requestId);

                var request = {id: requestId};
                _debug('Delivering comet request {}', requestId);
                self.deliver(packet, request);
                _cometRequest = request;
            };

            function _send(self, packet)
            {
                var requestId = ++_requestIds;
                _debug('Beginning request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);

                var request = {id: requestId};
                // Consider the comet request which should always be present
                if (_requests.length < _maxRequests - 1)
                {
                    _debug('Delivering request {}', requestId);
                    self.deliver(packet, request);
                    _requests.push(request);
                }
                else
                {
                    _packets.push([packet, request]);
                    _debug('Queued request {}, {} queued requests', requestId, _packets.length);
                }
            };

            this.complete = function(request, success, comet)
            {
                if (comet)
                    _cometComplete(request);
                else
                    _complete(this, request, success);
            };

            function _cometComplete(request)
            {
                var requestId = request.id;
                if (_cometRequest !== request) throw 'Comet request mismatch, completing request ' + requestId;

                // Reset comet request
                _cometRequest = null;
                _debug('Ended comet request {}', requestId);
            };

            function _complete(self, request, success)
            {
                var requestId = request.id;
                var index = $.inArray(request, _requests);
                // The index can be negative the request has been aborted
                if (index >= 0) _requests.splice(index, 1);
                _debug('Ended request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);

                if (_packets.length > 0)
                {
                    var packet = _packets.shift();
                    if (success)
                    {
                        _debug('Dequeueing and sending request {}, {} queued requests', packet[1].id, _packets.length);
                        _send(self, packet[0]);
                    }
                    else
                    {
                        _debug('Dequeueing and failing request {}, {} queued requests', packet[1].id, _packets.length);
                        // Keep the semantic of calling response callbacks asynchronously after the request
                        setTimeout(function() { packet[0].onFailure(packet[1], 'error'); }, 0);
                    }
                }
            };

            this.abort = function()
            {
                for (var i = 0; i < _requests.length; ++i)
                {
                    var request = _requests[i];
                    _debug('Aborting request {}', request.id);
                    if (request.xhr) request.xhr.abort();
                }
                if (_cometRequest)
                {
                    _debug('Aborting comet request {}', _cometRequest.id);
                    if (_cometRequest.xhr) _cometRequest.xhr.abort();
                }
                _cometRequest = null;
                _requests = [];
                _packets = [];
            };
        };

        var LongPollingTransport = function()
        {
            this.deliver = function(packet, request)
            {
                request.xhr = $.ajax({
                    url: packet.url,
                    type: 'POST',
                    contentType: 'text/json;charset=UTF-8',
                    beforeSend: function(xhr)
                    {
                        xhr.setRequestHeader('Connection', 'Keep-Alive');
                        return true;
                    },
                    data: JSON.stringify(packet.messages),
                    success: function(response) { packet.onSuccess(request, response); },
                    error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
                });
            };
        };

        var CallbackPollingTransport = function()
        {
            var _maxLength = 2000;
            this.deliver = function(packet, request)
            {
                // Microsoft Internet Explorer has a 2083 URL max length
                // We must ensure that we stay within that length
                var messages = JSON.stringify(packet.messages);
                // Encode the messages because all brackets, quotes, commas, colons, etc
                // present in the JSON will be URL encoded, taking many more characters
                var urlLength = packet.url.length + encodeURI(messages).length;
                _debug('URL length: {}', urlLength);
                // Let's stay on the safe side and use 2000 instead of 2083
                // also because we did not count few characters among which
                // the parameter name 'message' and the parameter 'jsonp',
                // which sum up to about 50 chars
                if (urlLength > _maxLength)
                {
                    var x = packet.messages.length > 1 ?
                            'Too many bayeux messages in the same batch resulting in message too big ' +
                            '(' + urlLength + ' bytes, max is ' + _maxLength + ') for transport ' + this.getType() :
                            'Bayeux message too big (' + urlLength + ' bytes, max is ' + _maxLength + ') ' +
                            'for transport ' + this.getType();
                    // Keep the semantic of calling response callbacks asynchronously after the request
                    _setTimeout(function() { packet.onFailure(request, 'error', x); }, 0);
                }
                else
                {
                    $.ajax({
                        url: packet.url,
                        type: 'GET',
                        dataType: 'jsonp',
                        jsonp: 'jsonp',
                        beforeSend: function(xhr)
                        {
                            xhr.setRequestHeader('Connection', 'Keep-Alive');
                            return true;
                        },
                        data:
                        {
                            // In callback-polling, the content must be sent via the 'message' parameter
                            message: messages
                        },
                        success: function(response) { packet.onSuccess(request, response); },
                        error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
                    });
                }
            };
        };
    };

    /**
     * The JS object that exposes the comet API to applications
     */
    $.cometd = new $.Cometd(); // The default instance

})(jQuery);


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
#: CometPlugin.php:116
msgid "Plugin to make updates using Comet and Bayeux."
msgstr ""


code:
# Translation of StatusNet - Comet to Belarusian (Taraškievica orthography) (Беларуская (тарашкевіца))
# Exported from translatewiki.net
#
# Author: EugeneZelenko
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:04+0000\n"
"Language-Team: Belarusian (Taraškievica orthography) <http://translatewiki."
"net/wiki/Portal:be-tarask>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-24 15:24:57+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: be-tarask\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
#, fuzzy
msgid "Plugin to make updates using Comet and Bayeux."
msgstr ""
"Дапаўненьне для абнаўленьняў у «рэальным часе» з выкарыстаньнем Comet/Bayeux."


code:
# Translation of StatusNet - Comet to Breton (Brezhoneg)
# Exported from translatewiki.net
#
# Author: Y-M D
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:04+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-24 15:24:57+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
#, fuzzy
msgid "Plugin to make updates using Comet and Bayeux."
msgstr ""
"Un astenn evit ober hizivadennoù \"war ar prim\" en ur implijout Comet/"
"Bayeux."


code:
# Translation of StatusNet - Comet to German (Deutsch)
# Exported from translatewiki.net
#
# Author: MF-Warburg
# Author: The Evil IP address
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: German <https://translatewiki.net/wiki/Portal:de>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Plugin, um Updates mit Comet und Bayeux durchzuführen."


code:
# Translation of StatusNet - Comet to Spanish (español)
# Exported from translatewiki.net
#
# Author: Erchache2000
# Author: Translationista
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Language-Team: Spanish <https://translatewiki.net/wiki/Portal:es>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: es\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr ""
"Extensión para hacer actualizaciones en \"tiempo real\" utilizando Comet y "
"Bayeux."


code:
# Translation of StatusNet - Comet to Basque (euskara)
# Exported from translatewiki.net
#
# Author: Artsuaga
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:04+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Comet and Bayeux erabiliz eguneraketa egiteko plugina."


code:
# Translation of StatusNet - Comet to Finnish (Suomi)
# Exported from translatewiki.net
#
# Author: Nike
# Author: XTL
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:04+0000\n"
"Language-Team: Finnish <http://translatewiki.net/wiki/Portal:fi>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-24 15:24:57+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: fi\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
#, fuzzy
msgid "Plugin to make updates using Comet and Bayeux."
msgstr ""
"Liitännäinen \"reaaliaikaisten\" päivitysten tekemiseen Comet/Bayeux-"
"yhteyskäytäntöjä käyttäen."


code:
# Translation of StatusNet - Comet to French (français)
# Exported from translatewiki.net
#
# Author: Peter17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:05+0000\n"
"Language-Team: French <https://translatewiki.net/wiki/Portal:fr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: fr\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Extension pour réaliser des mises à jour en utilisant Comet et Bayeux."


code:
# Translation of StatusNet - Comet to Galician (galego)
# Exported from translatewiki.net
#
# Author: Toliño
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:05+0000\n"
"Language-Team: Galician <https://translatewiki.net/wiki/Portal:gl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Complemento para realizar actualizacións mediante Comet e Bayeux."


code:
# Translation of StatusNet - Comet to Hebrew (עברית)
# Exported from translatewiki.net
#
# Author: Amire80
# Author: YaronSh
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:05+0000\n"
"Language-Team: Hebrew <https://translatewiki.net/wiki/Portal:he>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: he\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "תוסף לביצוע עדכונים באמצעות Comet ו־Bayeux."


code:
# Translation of StatusNet - Comet to Interlingua (interlingua)
# Exported from translatewiki.net
#
# Author: McDutchie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:05+0000\n"
"Language-Team: Interlingua <https://translatewiki.net/wiki/Portal:ia>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ia\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Plug-in pro facer actualisationes usante Comet e Bayeux."


code:
# Translation of StatusNet - Comet to Indonesian (Bahasa Indonesia)
# Exported from translatewiki.net
#
# Author: Farras
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:05+0000\n"
"Language-Team: Indonesian <http://translatewiki.net/wiki/Portal:id>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-24 15:24:57+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: id\n"
"X-Message-Group: #out-statusnet-plugin-comet\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
#, fuzzy
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Pengaya untuk membuat pemutakhiran langsung menggunakan Comet/Bayeux."


code:
# Translation of StatusNet - Comet to Italian (italiano)
# Exported from translatewiki.net
#
# Author: Ximo17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Comet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:05+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:49:53+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
#. TRANS: and Comet is a web application model.
msgid "Plugin to make updates using Comet and Bayeux."
msgstr "Plugin per effettuare aggiornamenti utilizzando Comet e Bayeux."