From 8e167315d69ab4d8af4e7997aabb390105f6d742 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Thu, 6 Jul 2017 03:39:54 +0500 Subject: [PATCH 1/8] fix notification about new DMs on my own twists --- js/twister_newmsgs.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/twister_newmsgs.js b/js/twister_newmsgs.js index 7a2c9c8..9fa6ec8 100644 --- a/js/twister_newmsgs.js +++ b/js/twister_newmsgs.js @@ -203,8 +203,11 @@ function requestDMsCount() { var newDMsUpdated; for (var u in dmUsers) { - if (dmUsers[u]) { - var dmData = dmUsers[u][0]; + if (!dmUsers[u]) + continue; + + var dmData = dmUsers[u][0]; + if (dmData.from !== defaultScreenName) { if (u in _lastDMIdPerUser && u in _newDMsPerUser) { if (dmData.id !== _lastDMIdPerUser[u]) { _newDMsPerUser[u] += dmData.id - _lastDMIdPerUser[u]; @@ -214,8 +217,8 @@ function requestDMsCount() { _newDMsPerUser[u] = dmData.id + 1; newDMsUpdated = true; } - _lastDMIdPerUser[u] = dmData.id; } + _lastDMIdPerUser[u] = dmData.id; } if (newDMsUpdated) { saveDMsToStorage(); From 043f9e874085369ce0f4b0008e89383b436022f0 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Wed, 12 Jul 2017 19:01:07 +0500 Subject: [PATCH 2/8] delete including of removed sript from options.html --- options.html | 1 - 1 file changed, 1 deletion(-) diff --git a/options.html b/options.html index 7035574..c115791 100644 --- a/options.html +++ b/options.html @@ -18,7 +18,6 @@ - From 61872ab4eb785e67d1f8b031611fa953ecf5ab92 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Wed, 12 Jul 2017 19:28:01 +0500 Subject: [PATCH 3/8] fix decision whether DM was sent by local or remote peer to mark it accordingly --- js/twister_formatpost.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/twister_formatpost.js b/js/twister_formatpost.js index acc3c97..dfba9cd 100644 --- a/js/twister_formatpost.js +++ b/js/twister_formatpost.js @@ -266,10 +266,10 @@ function setPostInfoSent(n, k, item) { // format dmdata (returned by getdirectmsgs) to display in conversation thread function postToElemDM(dmData, localUser, remoteUser) { var senderAlias = (dmData.from && dmData.from.length && dmData.from.charCodeAt(0)) - ? dmData.from : (dmData.fromMe ? localUser : remoteUser); + ? dmData.from : (dmData.fromMe || dmData.from === localUser ? localUser : remoteUser); var elem = $('#dm-chat-template').clone(true).appendTo(twister.html.detached) .removeAttr('id') - .addClass(dmData.fromMe ? 'sent' : 'received') + .addClass(dmData.fromMe || dmData.from === localUser ? 'sent' : 'received') ; var elemName = elem.find('.post-info-name') From 0133ffd486bae5c3a36c8fba6210589cb484b022 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Fri, 14 Jul 2017 00:56:30 +0500 Subject: [PATCH 4/8] tune DMS and mentions processing, add DMs caching to localStorage --- js/interface_common.js | 15 +- js/tmobile.js | 60 ++++--- js/twister_actions.js | 135 +++++++++++++-- js/twister_directmsg.js | 186 ++++++--------------- js/twister_newmsgs.js | 356 +++++++++++++++++++++++++++++++--------- js/twister_timeline.js | 17 +- 6 files changed, 501 insertions(+), 268 deletions(-) diff --git a/js/interface_common.js b/js/interface_common.js index 664bcf3..d2ea20c 100644 --- a/js/interface_common.js +++ b/js/interface_common.js @@ -672,17 +672,10 @@ function openMentionsModalHandler(peerAlias) { }); var req = queryStart(modal.content.find('.postboard-posts'), peerAlias, 'mention'); - modal.content.find('.postboard-news') - .on('click', - {req: req, cbFunc: (peerAlias === defaultScreenName) ? resetMentionsCount : ''}, - handleClickDisplayPendingTwists - ) - ; + modal.content.find('.postboard-news').on('click', {req: req}, handleClickDisplayPendingTwists); - if (peerAlias === defaultScreenName) { + if (peerAlias === defaultScreenName) modal.content.on('scroll', handleMentionsModalScroll); - resetMentionsCount(); - } } function openFollowersModal(peerAlias) { @@ -943,9 +936,9 @@ function addToCommonDMsList(list, targetAlias, message) { getFullname(targetAlias, item.find('a.post-info-name')); } - if (_newDMsPerUser[targetAlias] > 0) + if (twister.DMs[targetAlias].lengthNew > 0) item.addClass('new') - .find('.messages-qtd').text(_newDMsPerUser[targetAlias]).show(); + .find('.messages-qtd').text(twister.DMs[targetAlias].lengthNew).show(); var items = list.children(); for (var i = 0; i < items.length; i++) { diff --git a/js/tmobile.js b/js/tmobile.js index bf69bbe..7947829 100644 --- a/js/tmobile.js +++ b/js/tmobile.js @@ -273,23 +273,37 @@ var router=new $.mobile.Router( $.mobile.showPageLoadingMsg(); initializeTwister( true, true, function() { $.mobile.showPageLoadingMsg(); - requestDMsnippetList($('#directmsg .direct-messages-list')); + modalDMsSummaryDraw($('#directmsg .direct-messages-list')); }); }, dmchat: function(type,match,ui) { var params=router.getParams(match[1]); $.mobile.showPageLoadingMsg(); initializeTwister( true, true, function() { - var user = params.user; - var dmConvo = $('#dmchat .direct-messages-thread'); - $("#dmchat .rtitle").text("Chat @" + user); + var peerAlias = params.user; + var board = $('#dmchat .direct-messages-thread').empty(); + + $('#dmchat .rtitle').text('Chat @' + peerAlias); $("#dmchat textarea").val(""); - dmConvo.html(""); - installDMSendClick(); + installDMSendClick(peerAlias); $.mobile.showPageLoadingMsg(); - dmChatUser = user; - requestDmConversation(dmConvo,user); + + tmobileQueryReq = queryStart(board, peerAlias, 'direct', undefined, 2000, { + boardAutoAppend: true, + lastId: 0, + lengthNew: 0, + ready: function (req, peerAlias) { + twister.DMs[peerAlias] = twister.res[req]; + }, + readyReq: peerAlias, + drawFinish: function (req) { + setTimeout($.MAL.dmConversationLoaded, 200, twister.res[req].board); + }, + skidoo: function (req) { + return $.mobile.activePage.attr('id') !== 'dmchat' || req !== tmobileQueryReq; + } + }); }); }, search: function(type,match,ui) { @@ -390,23 +404,21 @@ function installSubmitClick() { }); } -function installDMSendClick() { - var $postSubmit = $(".dm-submit"); - $postSubmit.unbind('click').click(function(e){ - e.stopPropagation(); - e.preventDefault(); - var $this = $( this ); - var $replyText = $this.closest(".post-area-new").find("textarea"); +function installDMSendClick(peerAlias) { + $('.dm-submit').off('click').on('click', {peerAlias: peerAlias}, + function (event) { + muteEvent(event, true); - var $dmConversation = $(".directMessages"); + var elemTextArea = $(event.target).closest('.post-area-new').find('textarea'); + if (!elemTextArea.val()) + return; - var s = encode_utf8($replyText.val()); - newDirectMsg(s, dmChatUser); - $replyText.val(""); - }); + newDirectMsg(encode_utf8(elemTextArea.val()), event.data.peerAlias); + elemTextArea.val(''); + } + ); } - function installRetransmitConfirmClick() { var $postConfirmRt = $(".retransmit-confirm"); $postConfirmRt.unbind('click').click(function(e){ @@ -535,15 +547,13 @@ function setupHashtagOrMention(board, query, resource) { $.mobile.showPageLoadingMsg(); board.empty(); - var req = queryStart(board, query, resource, undefined, undefined, { + tmobileQueryReq = queryStart(board, query, resource, undefined, undefined, { boardAutoAppend: true, skidoo: function (req) { var curPage = $.mobile.activePage.attr('id'); return (curPage !== 'mentions' && curPage !== 'hashtag') || req !== tmobileQueryReq; } }); - - tmobileQueryReq = req; } // every 2 seconds do something page specific. @@ -565,8 +575,6 @@ function tmobileTick() { } }, {} ); } - if (curPage === 'dmchat') - requestDmConversation($('#dmchat .direct-messages-thread'), dmChatUser); } $(document).bind('mobileinit', function () { diff --git a/js/twister_actions.js b/js/twister_actions.js index 3626440..8cdd6c7 100644 --- a/js/twister_actions.js +++ b/js/twister_actions.js @@ -436,6 +436,24 @@ function updateProfilePosts(postsView, username, useGetposts) { }); } +function queryCreateRes(query, resource, extra) { + var req = query + '@' + resource; + twister.res[req] = { + query: query, + resource: resource, + lengthCached: 0, + twists: { + cached: {}, + pending: [] + } + }; + if (extra) + for (i in extra) + twister.res[req][i] = extra[i]; + + return twister.res[req]; +} + function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra) { var req = query + '@' + resource; @@ -444,6 +462,7 @@ function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra) board: board, query: query, resource: resource, + lengthCached: 0, twists: { cached: {}, pending: [] @@ -462,6 +481,15 @@ function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra) if (twister.res[req].twists.pending.indexOf(i) === -1) twister.res[req].twists.pending.push(i); + if (extra) { + if (typeof extra.drawFinish === 'function') { + twister.res[req].drawFinish = extra.drawFinish; + twister.res[req].drawFinishReq = extra.drawFinishReq; + } + if (typeof extra.skidoo === 'function') + twister.res[req].skidoo = extra.skidoo; + } + queryPendingDraw(req); } @@ -510,27 +538,45 @@ function queryRequest(req) { } else if (twister.res[req].resource === 'fav') twisterRpc('getfavs', [twister.res[req].query, 1000], queryProcess, req); - else + else if (twister.res[req].resource === 'direct') { + var lengthStandard = 100; // FIXME there may be the gap between .lastId and the lesser twist.id in response greater than 100 (very rare case) + if (twister.res[req].lengthCached < Math.min(twister.res[req].lastId, lengthStandard) + && !twister.res[req].triedToReCache) { + twister.res[req].triedToReCache = true; + var length = Math.min(twister.res[req].lastId + 1, lengthStandard); + var query = [{username: twister.res[req].query, max_id: twister.res[req].lastId}]; + } else + var length = lengthStandard, query = [{username: twister.res[req].query, since_id: twister.res[req].lastId}]; + + twisterRpc('getdirectmsgs', [defaultScreenName, length, query], + queryProcess, req, + function (req, res) { + console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res})); + } + ); + } else dhtget(twister.res[req].query, twister.res[req].resource, 'm', queryProcess, req, twister.res[req].timeoutArgs); } -function queryProcess(req, twists) { - if (!req || !twister.res[req] || !twists || !twists.length) +function queryProcess(req, res) { + if (!req || !twister.res[req] || typeof res !== 'object' || $.isEmptyObject(res)) return; var lengthNew = 0; var lengthPending = twister.res[req].twists.pending.length; if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) - lengthNew = queryPendingPushMentions(req, twists); + lengthNew = queryPendingPushMentions(req, res); + else if (twister.res[req].resource === 'direct') + lengthNew = queryPendingPushDMs(res); else - lengthNew = queryPendingPush(req, twists); + lengthNew = queryPendingPush(req, res); if (typeof twister.res[req].skidoo === 'function' && twister.res[req].skidoo(req)) return; - if (lengthNew) + if (lengthNew) { if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) { $.MAL.updateNewMentionsUI(twister.res[req].lengthNew); $.MAL.soundNotifyMentions(); @@ -550,6 +596,25 @@ function queryProcess(req, twists) { $.MAL.showMentions(defaultScreenName); }).bind({req: req}) }); + } else if (twister.res[req].resource === 'direct') { + if (twister.res[req].query[0] !== '*') + $.MAL.updateNewDMsUI(getNewDMsCount()); + else + $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); + + $.MAL.soundNotifyDM(); + if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable') + $.MAL.showDesktopNotification({ + body: twister.res[req].query[0] === '*' ? + polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', getNewGroupDMsCount()) + '.' + : polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', getNewDMsCount()) + '.', + tag: 'twister_notification_new_DMs', + timeout: $.Options.showDesktopNotifDMsTimer.val, + funcClick: (function () { + focusModalWithElement(twister.res[this.req].board); + }).bind({req: req}) + }); + // TODO new DMs counters on minimized modals' } else if (!$.mobile && $.Options.showDesktopNotifPostsModal.val === 'enable' && (twister.res[req].resource !== 'mention' || twister.res[req].query !== defaultScreenName) && twister.res[req].board && isModalWithElemExists(twister.res[req].board) @@ -559,7 +624,7 @@ function queryProcess(req, twists) { + polyglot.t('in search result') + '.', tag: 'twister_notification_new_posts_modal', timeout: $.Options.showDesktopNotifPostsModalTimer.val, - funcClick: (function() { + funcClick: (function () { focusModalWithElement(twister.res[this.req].board, function (req) { twister.res[req].board.closest('.postboard') @@ -569,6 +634,7 @@ function queryProcess(req, twists) { ); }).bind({req: req}) }); + } if (twister.res[req].twists.pending.length > lengthPending) { // there is some twists may be which are not considered new so lengthNew equals zero (mentions thing) if (!twister.res[req].board || (!$.mobile && !isModalWithElemExists(twister.res[req].board))) @@ -619,6 +685,7 @@ function queryPendingPush(req, twists) { lengthNew++; twister.res[req].twists.cached[j] = twists[i]; + twister.res[req].lengthCached++; twister.res[req].twists.pending.push(j); } } @@ -627,13 +694,57 @@ function queryPendingPush(req, twists) { } function queryPendingDraw(req) { - var twists = []; - for (var i = 0; i < twister.res[req].twists.pending.length; i++) - twists.push(twister.res[req].twists.cached[twister.res[req].twists.pending[i]]); + var twists = [], length = 0; + + if (twister.res[req].resource === 'direct') { + for (var j = 0; j < twister.res[req].twists.pending.length; j++) { + var twist = twister.res[req].twists.cached[twister.res[req].twists.pending[j]]; + for (var i = 0; i < length; i++) + if (twist.id < twists[i].id) { + twists.splice(i, 0, twist); + break; + } + + if (length === twists.length) + twists.push(twist); + + length++; + } + attachPostsToStream(twister.res[req].board, twists, false, + function (twist, req) { + return {item: postToElemDM(twist, req.peerAliasLocal, req.peerAliasRemote) + .attr('data-id', twist.id), time: twist.time}; + }, + {peerAliasLocal: defaultScreenName, peerAliasRemote: twister.res[req].query} + ); + resetNewDMsCountForPeer(twister.res[req].query); + } else { + for (var j = 0; j < twister.res[req].twists.pending.length; j++) { + var twist = twister.res[req].twists.cached[twister.res[req].twists.pending[j]]; + for (var i = 0; i < length; i++) + if (twist.userpost.time > twists[i].userpost.time) { + twists.splice(i, 0, twist); + break; + } + + if (length === twists.length) + twists.push(twist); - attachPostsToStream(twister.res[req].board, twists, false); + length++; + } + attachPostsToStream(twister.res[req].board, twists, true, + function (twist) { + return {item: postToElem(twist, 'original'), time: twist.userpost.time}; + } + ); + if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) + resetMentionsCount(); + } queryPendingClear(req); - $.MAL.postboardLoaded(); + if (typeof twister.res[req].drawFinish === 'function') + twister.res[req].drawFinish(req, twister.res[req].drawFinishReq); + else + $.MAL.postboardLoaded(); } diff --git a/js/twister_directmsg.js b/js/twister_directmsg.js index c21877d..f82cb8b 100644 --- a/js/twister_directmsg.js +++ b/js/twister_directmsg.js @@ -5,120 +5,6 @@ var _groupMsgInviteToGroupQueue = []; -function requestDMsnippetList(elemList, forGroup) { - var followList = []; - for (var i = 0; i < followingUsers.length; i++) - followList.push({username: followingUsers[i]}); - for (var i = 0; i < groupChatAliases.length; i++) - followList.push({username: groupChatAliases[i]}); - - twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList], - processDMsnippet, {elemList: elemList, forGroup: forGroup}, - function(req, ret) {console.log('ajax error:' + ret);}, null - ); -} - -function processDMsnippet(req, DMs) { - req.elemList.empty(); - - for (var alias in DMs) - if ((req.forGroup && alias[0] === '*') || (!req.forGroup && alias[0] !== '*')) - addToCommonDMsList(req.elemList, alias, DMs[alias][0]); - - $.MAL.commonDMsListLoaded(); -} - -function requestDmConversationModal(postboard, peerAlias) { - if (!isModalWithElemExists(postboard)) - return; - - requestDmConversation(postboard, peerAlias); - setTimeout(requestDmConversationModal, 1000, postboard, peerAlias); -} - -function requestDmConversation(postboard, peerAlias) { - var since_id = undefined; - - var oldItems = postboard.children(); - if (oldItems.length) - since_id = parseInt(oldItems.eq(oldItems.length - 1).attr('data-id')); - - var userDmReq = [{username: peerAlias}]; - if (typeof since_id !== 'undefined') - userDmReq[0].since_id = since_id; - - var count = 100; - twisterRpc('getdirectmsgs', [defaultScreenName, count, userDmReq], - function(req, ret) {processDmConversation(req.postboard, req.peerAlias, ret);}, - {postboard: postboard, peerAlias: peerAlias}, - function(req, ret) { - var msg = (ret.message) ? ret.message : ret; - alert(polyglot.t('ajax_error', {error: msg})); - } - ); -} - -function processDmConversation(stream, peerAlias, posts) { - if (!isModalWithElemExists(stream)) - return; - - var streamItems = stream.children(); - var streamPostsIDs = []; - var newPosts = 0; - - for (var i = 0; i < streamItems.length; i++) { - streamPostsIDs.push(parseInt(streamItems.eq(i).attr('data-id'))); - } - - if (posts[peerAlias] && posts[peerAlias].length) { - for (var i = 0; i < posts[peerAlias].length; i++) { - if (streamPostsIDs.indexOf(posts[peerAlias][i].id) === -1) { - var lastPostID = posts[peerAlias][i].id; - newPosts++; - postToElemDM(posts[peerAlias][i], defaultScreenName, peerAlias) - .attr('data-id', lastPostID) - .appendTo(stream) - ; - streamPostsIDs.push(lastPostID); - } - } - $.MAL.dmConversationLoaded(stream); - } - - if (newPosts) { - resetNewDMsCountForUser(peerAlias, lastPostID); - - if (getHashOfMinimizedModalWithElem(stream)) { - $.MAL.soundNotifyDM(); - _newDMsPerUser[peerAlias] += newPosts; - if (peerAlias[0] === '*') - $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); - else - $.MAL.updateNewDMsUI(getNewDMsCount()); - - if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable') - $.MAL.showDesktopNotification({ - body: peerAlias[0] === '*' ? - polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newPosts) + '.' - : polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newPosts) + '.', - tag: 'twister_notification_new_DMs', - timeout: $.Options.showDesktopNotifDMsTimer.val, - funcClick: (function() { - focusModalWithElement(this.postboard, - function (peerAlias) { - _newDMsPerUser[peerAlias] = 0; - if (peerAlias[0] === '*') - $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); - else - $.MAL.updateNewDMsUI(getNewDMsCount()); - }, this.peerAlias); - }).bind({postboard: stream, peerAlias: peerAlias}) - }); - // TODO here we need to set new DMs counter on minimized modal button - } - } -} - function directMsgSubmit(e) { e.stopPropagation(); e.preventDefault(); @@ -161,6 +47,20 @@ function newDirectMsg(msg, peerAlias) { alert(polyglot.t('Internal error: lastPostId unknown (following yourself may fix!)')); } +function modalDMsSummaryDraw(elem, group) { + elem.empty(); + + for (var peerAlias in twister.DMs) + if (group ? peerAlias[0] === '*' : peerAlias[0] !== '*') + for (var j in twister.DMs[peerAlias].twists.cached) + if (twister.DMs[peerAlias].lastId === twister.DMs[peerAlias].twists.cached[j].id) { + addToCommonDMsList(elem, peerAlias, twister.DMs[peerAlias].twists.cached[j]); + break; + } + + $.MAL.commonDMsListLoaded(); +} + // dispara o modal de direct messages function openCommonDMsModal() { if (!defaultScreenName) { @@ -174,19 +74,16 @@ function openCommonDMsModal() { title: polyglot.t('Direct Messages') }); - requestDMsnippetList(modal.content.find('.direct-messages-list')); + modalDMsSummaryDraw(modal.content.find('.direct-messages-list')); modal.self.find('.mark-all-as-read') .css('display', 'inline') .attr('title', polyglot.t('Mark all as read')) .on('click', function (event) { - for (var user in _newDMsPerUser) { - if (user[0] !== '*') - _newDMsPerUser[user] = 0; - } - saveDMsToStorage(); - $.MAL.updateNewDMsUI(getNewDMsCount()); - $(event.target).closest('.directMessages').find('.direct-messages-list .messages-qtd').hide(); + resetNewDMsCount(); + var elem = $(event.target).closest('.directMessages').find('.direct-messages-list'); + elem.find('.messages-qtd').hide(); + elem.find('.post.new').removeClass('new'); }) ; } @@ -210,7 +107,21 @@ function openDmWithUserModal(peerAlias) { else getFullname(peerAlias, modal.self.find('.modal-header h3 span')); - requestDmConversationModal(modal.self.find('.direct-messages-thread').empty(), peerAlias); + queryStart(modal.content.find('.direct-messages-thread'), + peerAlias, 'direct', undefined, 2000, { + boardAutoAppend: true, + lastId: 0, + lengthNew: 0, + ready: function (req, peerAlias) { + twister.DMs[peerAlias] = twister.res[req]; + }, + readyReq: peerAlias, + drawFinish: function (req) { + $.MAL.dmConversationLoaded(twister.res[req].board); + } + } + ); + modal.content.on('scroll', {req: peerAlias}, handleDMsModalScroll); $('.dm-form-template').children().clone(true) .addClass('open').appendTo(modal.content).fadeIn('fast') @@ -232,19 +143,16 @@ function openGroupMessagesModal(groupAlias) { modal.content.prepend($('#group-messages-profile-modal-control-template').children().clone(true)); - requestDMsnippetList(modal.content.find('.direct-messages-list'), true); + modalDMsSummaryDraw(modal.content.find('.direct-messages-list'), true); modal.self.find('.mark-all-as-read') .css('display', 'inline') .attr('title', polyglot.t('Mark all as read')) .on('click', function (event) { - for (var user in _newDMsPerUser) { - if (user[0] === '*') - _newDMsPerUser[user] = 0; - } - saveDMsToStorage(); - $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); - $(event.target).closest('.groupMessages').find('.direct-messages-list .messages-qtd').hide(); + resetNewDMsCountGroup(); + var elem = $(event.target).closest('.groupMessages').find('.direct-messages-list'); + elem.find('.messages-qtd').hide(); + elem.find('.post.new').removeClass('new'); }) ; } else { @@ -261,7 +169,21 @@ function openGroupMessagesModal(groupAlias) { function(req, ret) { if (ret && ret.members.indexOf(defaultScreenName) !== -1) { req.modal.content.append($('.messages-thread-template').children().clone(true)); - requestDmConversationModal(req.modal.content.find('.direct-messages-thread'), req.groupAlias); + queryStart(req.modal.content.find('.direct-messages-thread'), + req.groupAlias, 'direct', undefined, 2000, { + boardAutoAppend: true, + lastId: 0, + lengthNew: 0, + ready: function (req, peerAlias) { + twister.DMs[peerAlias] = twister.res[req]; + }, + readyReq: req.groupAlias, + drawFinish: function (req) { + $.MAL.dmConversationLoaded(twister.res[req].board); + } + } + ); + modal.content.on('scroll', {req: req.groupAlias}, handleDMsModalScroll); var control = $('#group-messages-messages-modal-control-template').children().clone(true) .appendTo(req.modal.content); diff --git a/js/twister_newmsgs.js b/js/twister_newmsgs.js index 9fa6ec8..a648a7e 100644 --- a/js/twister_newmsgs.js +++ b/js/twister_newmsgs.js @@ -42,6 +42,7 @@ function loadMentionsFromStorage() { var j = mentions.twists[i].userpost.n + '/' + mentions.twists[i].userpost.time; if (typeof twister.mentions.twists.cached[j] === 'undefined') { twister.mentions.twists.cached[j] = mentions.twists[i]; + twister.mentions.lengthCached++; if (twister.mentions.twists.cached[j].isNew) twister.mentions.lengthNew++; @@ -61,6 +62,7 @@ function loadMentionsFromStorage() { var j = mentions[i].data.userpost.n + '/' + mentions[i].mentionTime; if (typeof twister.mentions.twists.cached[j] === 'undefined') { twister.mentions.twists.cached[j] = mentions[i].data; + twister.mentions.lengthCached++; if (twister.mentions.twists.cached[j].isNew) twister.mentions.lengthNew++; @@ -104,6 +106,7 @@ function queryPendingPushMentions(req, res) { var j = res[i].userpost.n + '/' + res[i].userpost.time; if (typeof twister.res[req].twists.cached[j] === 'undefined') { twister.res[req].twists.cached[j] = res[i]; + twister.res[req].lengthCached++; twister.res[req].twists.pending.push(j); // mention must be somewhat recent compared to last known one to be considered new @@ -159,10 +162,10 @@ function handleMentionsModalScroll(event) { if (elem.scrollTop() >= elem[0].scrollHeight - elem.height() - 50) { twister.mentions.scrollQueryActive = true; - twisterRpc('getmentions', [twister.mentions.query, 10, + twisterRpc('getmentions', [twister.mentions.query, postsPerRefresh, {max_id: twister.mentions.lastTorrentId - twister.mentions.lengthFromTorrent}], function (req, res) { - twister.mentions.scrollQueryActive = false; + twister.res[req].scrollQueryActive = false; twister.res[req].boardAutoAppend = true; // FIXME all pending twists will be appended queryProcess(req, res); twister.res[req].boardAutoAppend = false; @@ -174,119 +177,274 @@ function handleMentionsModalScroll(event) { // --- direct messages --- -var _lastDMIdPerUser = {}; -var _newDMsPerUser = {}; - function saveDMsToStorage() { - var ns = $.initNamespaceStorage(defaultScreenName); - ns.localStorage.set('lastDMIdPerUser', _lastDMIdPerUser); - ns.localStorage.set('newDMsPerUser', _newDMsPerUser); + var pool = {}; + + for (var peerAlias in twister.DMs) { + var twists = [], length = 0; + for (var j in twister.DMs[peerAlias].twists.cached) { + for (var i = 0; i < length; i++) + if (twister.DMs[peerAlias].twists.cached[j].id > twists[i].id) { + twists.splice(i, 0, twister.DMs[peerAlias].twists.cached[j]); + break; + } + + if (length === twists.length) + twists.push(twister.DMs[peerAlias].twists.cached[j]); + + length++; + } + pool[peerAlias] = { + twists: twists.slice(0, 100), // TODO add an option to specify number of DMs to cache + lastId: twister.DMs[peerAlias].lastId, + }; + } + + $.initNamespaceStorage(defaultScreenName).localStorage.set('DMs', pool); } function loadDMsFromStorage() { - var ns = $.initNamespaceStorage(defaultScreenName); - if (ns.localStorage.isSet('lastDMIdPerUser')) - _lastDMIdPerUser = ns.localStorage.get('lastDMIdPerUser'); - if (ns.localStorage.isSet('newDMsPerUser')) - _newDMsPerUser = ns.localStorage.get('newDMsPerUser'); + var storage = $.initNamespaceStorage(defaultScreenName).localStorage; + + if (storage.isSet('DMs')) { + var pool = storage.get('DMs'); + if (typeof pool === 'object') { + for (var peerAlias in pool) { + if (!twister.DMs[peerAlias]) + twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', + {boardAutoAppend: true, lastId: 0, lengthNew: 0}); + + for (var i = 0; i < pool[peerAlias].twists.length; i++) { + var j = pool[peerAlias].twists[i].from + '/' + pool[peerAlias].twists[i].time; + if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') { + twister.DMs[peerAlias].twists.cached[j] = pool[peerAlias].twists[i]; + twister.DMs[peerAlias].lengthCached++; + if (twister.DMs[peerAlias].twists.cached[j].isNew) + twister.DMs[peerAlias].lengthNew++; + } + } + twister.DMs[peerAlias].lastId = pool[peerAlias].lastId; + } + } + } + + // WARN all following storage keys are deprecated (see commit FIXME) + if (storage.isSet('lastDMIdPerUser')) { + var pool = storage.get('lastDMIdPerUser'); + if (typeof pool === 'object') + for (var peerAlias in pool) { + if (!twister.DMs[peerAlias]) + twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', + {boardAutoAppend: true, lastId: 0, lengthNew: 0}); + + twister.DMs[peerAlias].lastId = pool[peerAlias]; + } + + storage.remove('lastDMIdPerUser'); + } + if (storage.isSet('newDMsPerUser')) { + var pool = storage.get('newDMsPerUser'); + if (typeof pool === 'object') + for (var peerAlias in pool) { + if (!twister.DMs[peerAlias]) + twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', + {boardAutoAppend: true, lastId: 0, lengthNew: 0}); + + twister.DMs[peerAlias].lengthNew = pool[peerAlias]; + } + + storage.remove('newDMsPerUser'); + } +} + +function queryPendingPushDMs(res) { + var lengthNew = 0; + var lengthPending = 0; + + for (var peerAlias in res) { + if (!res[peerAlias] || !res[peerAlias].length || !twister.DMs[peerAlias]) + continue; + + for (var i = 0; i < res[peerAlias].length; i++) { + var j = res[peerAlias][i].from + '/' + res[peerAlias][i].time; + if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') { + twister.DMs[peerAlias].twists.cached[j] = res[peerAlias][i]; + twister.DMs[peerAlias].lengthCached++; + twister.DMs[peerAlias].twists.pending.push(j); + lengthPending++; + if (twister.DMs[peerAlias].lastId < res[peerAlias][i].id) { + twister.DMs[peerAlias].lastId = res[peerAlias][i].id; + if ((!twister.DMs[peerAlias].board || !twister.DMs[peerAlias].board.is('html *')) + && !res[peerAlias][i].fromMe && res[peerAlias][i].from !== defaultScreenName) { + lengthNew++; + twister.DMs[peerAlias].lengthNew += 1; + twister.DMs[peerAlias].twists.cached[j].isNew = true; + } + } + } + } + } + + if (lengthPending) + saveDMsToStorage(); + + return lengthNew; } function requestDMsCount() { - var followList = []; + var list = []; for (var i = 0; i < followingUsers.length; i++) - followList.push({username: followingUsers[i]}); - for (var i = 0; i < groupChatAliases.length; i++ ) - followList.push({username: groupChatAliases[i]}); + list.push({username: followingUsers[i]}); + for (var i = 0; i < groupChatAliases.length; i++) + list.push({username: groupChatAliases[i]}); - twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList], - function(req, dmUsers) { - var newDMsUpdated; + twisterRpc('getdirectmsgs', [defaultScreenName, 1, list], + function (req, res) { + var lengthNew = 0, lengthNewMax = 0; + var list = []; - for (var u in dmUsers) { - if (!dmUsers[u]) + for (var peerAlias in res) { + if (!res[peerAlias] || !res[peerAlias].length) continue; - var dmData = dmUsers[u][0]; - if (dmData.from !== defaultScreenName) { - if (u in _lastDMIdPerUser && u in _newDMsPerUser) { - if (dmData.id !== _lastDMIdPerUser[u]) { - _newDMsPerUser[u] += dmData.id - _lastDMIdPerUser[u]; - newDMsUpdated = true; - } - } else { - _newDMsPerUser[u] = dmData.id + 1; - newDMsUpdated = true; - } - } - _lastDMIdPerUser[u] = dmData.id; + if (!twister.DMs[peerAlias]) + twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', + {boardAutoAppend: true, lastId: 0, lengthNew: 0}); + + if (res[peerAlias][0].id > twister.DMs[peerAlias].lastId) { + lengthNew = res[peerAlias][0].id - twister.DMs[peerAlias].lastId; + if (lengthNewMax < lengthNew) + lengthNewMax = lengthNew; + + list.push({username: peerAlias}); + } else if (!twister.DMs[peerAlias].lengthCached) + queryPendingPushDMs(res); } - if (newDMsUpdated) { - saveDMsToStorage(); - var newDMs = getNewDMsCount(); - if (newDMs) { - $.MAL.updateNewDMsUI(newDMs); - $.MAL.soundNotifyDM(); - - if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable') { - $.MAL.showDesktopNotification({ - body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newDMs) + '.', - tag: 'twister_notification_new_DMs', - timeout: $.Options.showDesktopNotifDMsTimer.val, - funcClick: function () {$.MAL.showDMchat();} - }); - } - } - var newDMs = getNewGroupDMsCount(); - if (newDMs) { - $.MAL.updateNewGroupDMsUI(newDMs); - $.MAL.soundNotifyDM(); - - if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable') { - $.MAL.showDesktopNotification({ - body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newDMs) + '.', - tag: 'twister_notification_new_DMs', - timeout: $.Options.showDesktopNotifDMsTimer.val, - funcClick: function () {$.MAL.showDMchat({group: true});} - }); + + if (list.length === 1) + queryProcess(list[0].username + '@direct', res); + else if (lengthNewMax === 1) { + if (queryPendingPushDMs(res)) + DMsSummaryProcessNew(); + } else if (lengthNewMax) { + twisterRpc('getdirectmsgs', [defaultScreenName, lengthNewMax, list], + function (req, res) { + if (typeof res !== 'object' || $.isEmptyObject(res)) + return; + + if (queryPendingPushDMs(res)) + DMsSummaryProcessNew(); + }, undefined, + function (req, res) { + console.warn(polyglot.t('ajax_error', + {error: (res && res.message) ? res.message : res})); } - } + ); } - }, null, - function(req, ret) {console.warn('ajax error:' + ret);}, null + }, undefined, + function (req, res) { + console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res})); + } ); } +function DMsSummaryProcessNew() { + var lengthNew = getNewDMsCount(); + if (lengthNew) { + $.MAL.updateNewDMsUI(lengthNew); + $.MAL.soundNotifyDM(); + if (!$.mobile) { + if ($.Options.showDesktopNotifDMs.val === 'enable') { + $.MAL.showDesktopNotification({ + body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', lengthNew) + '.', + tag: 'twister_notification_new_DMs', + timeout: $.Options.showDesktopNotifDMsTimer.val, + funcClick: function () {$.MAL.showDMchat();} + }); + } + var elem = getElem('.directMessages .direct-messages-list'); + if (isModalWithElemExists(elem)) + modalDMsSummaryDraw(elem); + } else if ($.mobile.activePage.attr('id') !== 'directmsg') + modalDMsSummaryDraw($('#directmsg .direct-messages-list')); + } + lengthNew = getNewGroupDMsCount(); + if (lengthNew) { + $.MAL.updateNewGroupDMsUI(lengthNew); + $.MAL.soundNotifyDM(); + if (!$.mobile) { + if ($.Options.showDesktopNotifDMs.val === 'enable') { + $.MAL.showDesktopNotification({ + body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', lengthNew) + '.', + tag: 'twister_notification_new_DMs', + timeout: $.Options.showDesktopNotifDMsTimer.val, + funcClick: function () {$.MAL.showDMchat({group: true});} + }); + } + var elem = getElem('.groupMessages .direct-messages-list'); + if (isModalWithElemExists(elem)) + modalDMsSummaryDraw(elem, true); + } else if ($.mobile.activePage.attr('id') !== 'directmsg') + modalDMsSummaryDraw($('#directmsg .direct-messages-list'), true); + } +} + function getNewDMsCount() { - var newDMs = 0; + var lengthNew = 0; - for (var user in _newDMsPerUser) { - if (user[0] !== '*' && _newDMsPerUser[user]) - newDMs += _newDMsPerUser[user]; - } + for (var peerAlias in twister.DMs) + if (peerAlias[0] !== '*' && twister.DMs[peerAlias].lengthNew) + lengthNew += twister.DMs[peerAlias].lengthNew; - return newDMs; + return lengthNew; } function getNewGroupDMsCount() { - var newDMs = 0; + var lengthNew = 0; - for (var user in _newDMsPerUser) { - if (user[0] === '*' && _newDMsPerUser[user]) - newDMs += _newDMsPerUser[user]; - } + for (var peerAlias in twister.DMs) + if (peerAlias[0] === '*' && twister.DMs[peerAlias].lengthNew) + lengthNew += twister.DMs[peerAlias].lengthNew; - return newDMs; + return lengthNew; } -function resetNewDMsCountForUser(user, lastId) { - _newDMsPerUser[user] = 0; - _lastDMIdPerUser[user] = lastId; +function resetNewDMsCount() { + for (var peerAlias in twister.DMs) + if (peerAlias[0] !== '*') { + twister.DMs[peerAlias].lengthNew = 0; + for (var j in twister.DMs[peerAlias].twists.cached) + delete twister.DMs[peerAlias].twists.cached[j].isNew; + } saveDMsToStorage(); $.MAL.updateNewDMsUI(getNewDMsCount()); +} + +function resetNewDMsCountGroup() { + for (var peerAlias in twister.DMs) + if (peerAlias[0] === '*') { + twister.DMs[peerAlias].lengthNew = 0; + for (var j in twister.DMs[peerAlias].twists.cached) + delete twister.DMs[peerAlias].twists.cached[j].isNew; + } + + saveDMsToStorage(); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); } +function resetNewDMsCountForPeer(peerAlias) { + twister.DMs[peerAlias].lengthNew = 0; + for (var j in twister.DMs[peerAlias].twists.cached) + delete twister.DMs[peerAlias].twists.cached[j].isNew; + + saveDMsToStorage(); + if (peerAlias[0] !== '*') + $.MAL.updateNewDMsUI(getNewDMsCount()); + else + $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); +} + function updateGroupList() { twisterRpc('listgroups', [], function(req, ret) {groupChatAliases = ret;}, null, @@ -295,6 +453,7 @@ function updateGroupList() { } function initDMsCount() { + twister.DMs = {}; loadDMsFromStorage(); $.MAL.updateNewDMsUI(getNewDMsCount()); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); @@ -310,3 +469,38 @@ function initDMsCount() { function newmsgsChangedUser() { clearInterval(twister.mentions.interval); } + +function handleDMsModalScroll(event) { + if (!event || !event.data.req || !twister.DMs[event.data.req] + || twister.DMs[event.data.req].scrollQueryActive) + return; + + var length = twister.DMs[event.data.req].lastId - twister.DMs[event.data.req].lengthCached + 1; + if (!length) + return; + + var elem = $(event.target); + if (elem.scrollTop() < 100) { + twister.DMs[event.data.req].scrollQueryActive = true; + + twisterRpc('getdirectmsgs', [defaultScreenName, Math.min(length, postsPerRefresh), + [{username: twister.DMs[event.data.req].query, max_id: length - 1}]], + function (req, res) { + twister.res[req.k].scrollQueryActive = false; + //twister.res[req.k].boardAutoAppend = true; // FIXME all pending twists will be appended + queryProcess(req.k, res); + //twister.res[req.k].boardAutoAppend = false; + if (req.container[0].scrollHeight !== req.containerScrollHeightPrev) + req.container.scrollTop(req.container[0].scrollHeight - req.containerScrollHeightPrev); + }, { + k: twister.DMs[event.data.req].query + '@' + twister.DMs[event.data.req].resource, + container: elem, + containerScrollHeightPrev: elem[0].scrollHeight + }, + function (req, res) { + console.warn(polyglot.t('ajax_error', + {error: (res && res.message) ? res.message : res})); + } + ); + } +} diff --git a/js/twister_timeline.js b/js/twister_timeline.js index 09c4010..80c8542 100644 --- a/js/twister_timeline.js +++ b/js/twister_timeline.js @@ -183,13 +183,18 @@ function processReceivedPosts(req, posts) } function updateTimeline(req, posts) { - attachPostsToStream($.MAL.getStreamPostsParent(), posts, req.getspam); + attachPostsToStream($.MAL.getStreamPostsParent(), posts, true, + function (twist, promoted) { + return {item: postToElem(twist, 'original', promoted), time: twist.userpost.time}; + }, + req.getspam + ); for (var i = 0; i < posts.length; i++) { req.reportProcessedPost(posts[i]['userpost']['n'], posts[i]['userpost']['k'], true); } } -function attachPostsToStream(stream, posts, isPromoted) { +function attachPostsToStream(stream, posts, descendingOrder, createElem, createElemReq) { //console.log('attachPostsToStream:'); //console.log(posts); @@ -204,7 +209,7 @@ function attachPostsToStream(stream, posts, isPromoted) { for (var i = 0; i < posts.length; i++) { //console.log(posts[i]); var isAttached = false; - var intrantPost = {item: postToElem(posts[i], 'original', isPromoted), time: posts[i].userpost.time}; + var intrantPost = createElem(posts[i], createElemReq); intrantPost.item.attr('data-time', intrantPost.time); if (streamPosts.length) { @@ -213,10 +218,10 @@ function attachPostsToStream(stream, posts, isPromoted) { if (intrantPost.time === streamPosts[j].time && intrantPost.item[0].innerHTML === streamPosts[j].item[0].innerHTML) { isAttached = true; - console.log('appending of duplicate twist prevented'); + console.warn('appending of duplicate twist prevented'); break; - } else if (intrantPost.time > streamPosts[j].time) { - // this post in stream is older, so post must be inserted above + } else if (descendingOrder ? + intrantPost.time > streamPosts[j].time : intrantPost.time < streamPosts[j].time) { intrantPost.item.insertBefore(streamPosts[j].item).show(); streamPosts.splice(j, 0, intrantPost); isAttached = true; From 4bac2d579af8e599437c96332d00df3d808bb5ca Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Fri, 14 Jul 2017 03:12:35 +0500 Subject: [PATCH 5/8] add encryption of DMs's data cache --- home.html | 1 + js/twister_newmsgs.js | 30 +++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/home.html b/home.html index d83a2fe..d11f9f5 100644 --- a/home.html +++ b/home.html @@ -28,6 +28,7 @@ + diff --git a/js/twister_newmsgs.js b/js/twister_newmsgs.js index a648a7e..5d175a8 100644 --- a/js/twister_newmsgs.js +++ b/js/twister_newmsgs.js @@ -200,6 +200,8 @@ function saveDMsToStorage() { }; } + pool = twister.var.key.pub.encrypt(JSON.stringify(pool)); + delete pool.orig; // WORKAROUND the decrypt function does .slice(0, orig) but something goes wrong in process of buffer decoding (if original string contains non-ASCII characters) and orig may be smaller than the actual size, if it is undefined .slice gets it whole $.initNamespaceStorage(defaultScreenName).localStorage.set('DMs', pool); } @@ -208,6 +210,12 @@ function loadDMsFromStorage() { if (storage.isSet('DMs')) { var pool = storage.get('DMs'); + if (pool.key && pool.body && pool.mac) { + if (pool = twister.var.key.decrypt(pool)) + pool = JSON.parse(pool.toString()); + else + console.warn('can\'t decrypt DMs\' data cache'); + } if (typeof pool === 'object') { for (var peerAlias in pool) { if (!twister.DMs[peerAlias]) @@ -454,16 +462,20 @@ function updateGroupList() { function initDMsCount() { twister.DMs = {}; - loadDMsFromStorage(); - $.MAL.updateNewDMsUI(getNewDMsCount()); - $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); - //quick hack to obtain list of group chat aliases - updateGroupList(); - setInterval(updateGroupList, 60000); + dumpPrivkey(defaultScreenName, function (req, res) { + twister.var.key = TwisterCrypto.PrivKey.fromWIF(res); - setTimeout(requestDMsCount, 200); - //polling not needed: processNewPostsConfirmation will call requestDMsCount. - //setInterval('requestDMsCount()', 5000); + loadDMsFromStorage(); + $.MAL.updateNewDMsUI(getNewDMsCount()); + $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); + //quick hack to obtain list of group chat aliases + updateGroupList(); + setInterval(updateGroupList, 60000); + + setTimeout(requestDMsCount, 200); + //polling not needed: processNewPostsConfirmation will call requestDMsCount. + //setInterval('requestDMsCount()', 5000); + }); } function newmsgsChangedUser() { From 9a97fd03288e97b0d855d9259898f1290d1bd1c4 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Fri, 14 Jul 2017 03:17:18 +0500 Subject: [PATCH 6/8] add option to enable/disable encryption of DMs's data cache, separate section for DMs in options --- js/interface_localization.js | 39 ++++++++++++++++++++++++------------ js/options.js | 4 ++++ js/twister_newmsgs.js | 6 ++++-- options.html | 35 +++++++++++++++++++++++--------- theme_nin/css/style.css | 2 +- theme_nin/js/theme_option.js | 12 ++++------- theme_nin/sass/_tabs.sass | 5 ++--- 7 files changed, 67 insertions(+), 36 deletions(-) diff --git a/js/interface_localization.js b/js/interface_localization.js index abf858c..945a05c 100644 --- a/js/interface_localization.js +++ b/js/interface_localization.js @@ -367,7 +367,8 @@ if(preferredLanguage == "en"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Terminate Daemon": "Terminate Daemon", "New post": "New post", "Search": "Search", @@ -736,7 +737,8 @@ if(preferredLanguage == "es"){ "Language": "Idioma", "Sound": "Sonido", "Users": "Usuarios", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -1099,7 +1101,8 @@ if(preferredLanguage == "uk"){ "Language": "Мова", "Sound": "Звук", "Users": "Користувачі", - "Direct Message's copy to self": "Повідомлення скопійовано самому собі", + 'dm_copy_outgoing_to_self': 'Повідомлення скопійовано самому собі', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Статистика трафіку", "Direct messages with": "Співбесіда з", "DHT Torrents:": "DHT Torrents:", @@ -1465,7 +1468,8 @@ if(preferredLanguage == "zh-CN"){ "Language": "语言", "Sound": "声音", "Users": "用户", - "Direct Message's copy to self": "将私信发一份副本给我", + 'dm_copy_outgoing_to_self': '将私信发一份副本给我', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Terminate Daemon": "终止后台进程", "New post": "新推文", "Search": "搜索", @@ -1835,7 +1839,8 @@ if(preferredLanguage == "nl"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -2199,7 +2204,8 @@ if(preferredLanguage == "it"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -2565,7 +2571,8 @@ if(preferredLanguage == "fr"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -2932,7 +2939,8 @@ if(preferredLanguage == "ru"){ "Language": "Язык", "Sound": "Звук", "Users": "Пользователи", - "Direct Message's copy to self": "Синхронизировать отправленные личные сообщения", + 'dm_copy_outgoing_to_self': 'Синхронизировать отправленные личные сообщения', + 'dm_encrypt_local_cache': 'Шифровать локальный кэш данных', "Terminate Daemon": "Выключить twister демон", "New post": "Новый пост", "Search": "Поиск", @@ -3304,7 +3312,8 @@ if(preferredLanguage == "de"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Kopie der Direktnachricht an mich selbst", + 'dm_copy_outgoing_to_self': 'Kopie der Direktnachricht an mich selbst', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -3667,7 +3676,8 @@ if(preferredLanguage == "ja"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -4035,7 +4045,8 @@ if(preferredLanguage == "pt-BR"){ "Language": "Language", "Sound": "Sound", "Users": "Users", - "Direct Message's copy to self": "Direct Message's copy to self", + 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Traffic information", "DHT Torrents:": "DHT Torrents:", "Peers:": "Peers:", @@ -4400,7 +4411,8 @@ if(preferredLanguage == "tr"){ "Language": "Dil", "Sound": "Ses", "Users": "Kullanıcılar", - "Direct Message's copy to self": "Özel iletinin kopyasını sakla", + 'dm_copy_outgoing_to_self': 'Özel iletinin kopyasını sakla', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "Traffic information": "Trafik bilgileri", "DHT Torrents:": "DHT Torrentleri:", "Peers:": "Eşler:", @@ -4765,7 +4777,8 @@ if(preferredLanguage == "cs"){ "Language": "Jazyk", "Sound": "Zvuky", "Users": "Uživatelé", - "Direct Message's copy to self": "Posílat kopie přímých zpráv sám sobě", + 'dm_copy_outgoing_to_self': 'Posílat kopie přímých zpráv sám sobě', + 'dm_encrypt_local_cache': 'Encrypt local data cache', "New post": "Nový příspěvek", "Search": "Hledat", "Direct Msg": "Přímá zpráva", diff --git a/js/options.js b/js/options.js index 84f17cb..0eac0a1 100644 --- a/js/options.js +++ b/js/options.js @@ -227,6 +227,10 @@ function twisterOptions() { name: 'dmCopySelf', valDefault: 'enable' }); + this.add({ + name: 'dmEncryptCache', + valDefault: 'enable' + }); this.add({ name: 'hideReplies', valDefault: 'following' diff --git a/js/twister_newmsgs.js b/js/twister_newmsgs.js index 5d175a8..dc147ff 100644 --- a/js/twister_newmsgs.js +++ b/js/twister_newmsgs.js @@ -200,8 +200,10 @@ function saveDMsToStorage() { }; } - pool = twister.var.key.pub.encrypt(JSON.stringify(pool)); - delete pool.orig; // WORKAROUND the decrypt function does .slice(0, orig) but something goes wrong in process of buffer decoding (if original string contains non-ASCII characters) and orig may be smaller than the actual size, if it is undefined .slice gets it whole + if ($.Options.get('dmEncryptCache') === 'enable') { + pool = twister.var.key.pub.encrypt(JSON.stringify(pool)); + delete pool.orig; // WORKAROUND the decrypt function does .slice(0, orig) but something goes wrong in process of buffer decoding (if original string contains non-ASCII characters) and orig may be smaller than the actual size, if it is undefined .slice gets it whole + } $.initNamespaceStorage(defaultScreenName).localStorage.set('DMs', pool); } diff --git a/options.html b/options.html index c115791..1cc3a23 100644 --- a/options.html +++ b/options.html @@ -52,6 +52,8 @@ + + @@ -194,6 +196,30 @@ +
+
+

Direct Messages

+
+
+

dm_copy_outgoing_to_self

+ +
+
+
+
+

dm_encrypt_local_cache

+ +
+
+
+
+

Keys

@@ -457,15 +483,6 @@
-
-
-

Direct Message's copy to self

- -
-
diff --git a/theme_nin/css/style.css b/theme_nin/css/style.css index dd551e0..1504e1a 100644 --- a/theme_nin/css/style.css +++ b/theme_nin/css/style.css @@ -1415,7 +1415,7 @@ button:disabled:hover, button.disabled:hover, .mini-profile-actions span.disable visibility: hidden; } /* line 42, ../sass/_tabs.sass */ -.options input#tab_language:checked ~ .tab-content .language, .options input#t-2:checked ~ .tab-content .theme, .options input#t-3:checked ~ .tab-content .notifications, .options input#t-4:checked ~ .tab-content .keys, .options input#t-5:checked ~ .tab-content .appearance, .options input#t-6:checked ~ .tab-content .users, .options input#t-7:checked ~ .tab-content .webtorrent { +.options input#tab_language:checked ~ .tab-content .language, .options input#t-2:checked ~ .tab-content .theme, .options input#t-3:checked ~ .tab-content .notifications, .options input#t-4:checked ~ .tab-content .keys, .options input#t-5:checked ~ .tab-content .appearance, .options input#t-6:checked ~ .tab-content .users, .options input#t-7:checked ~ .tab-content .webtorrent, .options input#t-8:checked ~ .tab-content .DMs { position: relative; z-index: 10; opacity: 1; diff --git a/theme_nin/js/theme_option.js b/theme_nin/js/theme_option.js index fdcb3a5..95371b0 100644 --- a/theme_nin/js/theme_option.js +++ b/theme_nin/js/theme_option.js @@ -53,12 +53,8 @@ $(function(){ }); function localizeLabels() { - $("label[for=tab_language]").text(polyglot.t("Language")); - $("label[for=t-2]").text(polyglot.t("Theme")); - $("label[for=t-3]").text(polyglot.t("Notifications")); - $("label[for=t-4]").text(polyglot.t("Keys")); - $("label[for=t-5]").text(polyglot.t("Appearance")); - $("label[for=t-6]").text(polyglot.t("Users")); - $("label[for=t-7]").text(polyglot.t("WebTorrent")); + $('label.tabs').each(function (i, elem) { + var elem = $(elem); + elem.text(polyglot.t(elem.text())); + }); } - diff --git a/theme_nin/sass/_tabs.sass b/theme_nin/sass/_tabs.sass index c403c22..1a40dac 100644 --- a/theme_nin/sass/_tabs.sass +++ b/theme_nin/sass/_tabs.sass @@ -48,6 +48,7 @@ &#t-5:checked ~ .tab-content .appearance, &#t-6:checked ~ .tab-content .users &#t-7:checked ~ .tab-content .webtorrent + &#t-8:checked ~ .tab-content .DMs position: relative z-index: 10 opacity: 1 @@ -59,7 +60,7 @@ text-align: center width: auto display: inline-block!important - margin: 0 5px 0 0 + margin: 0 5px 0 0 padding: 5px 15px color: $main-color-light background: $background-light @@ -67,5 +68,3 @@ input:checked + label.tabs background: $bloc-background-color color: $main-color-dark - - From 366540889cd11dde19fc28e01534578c4b56f5e7 Mon Sep 17 00:00:00 2001 From: Simon Grim Date: Fri, 14 Jul 2017 03:44:40 +0500 Subject: [PATCH 7/8] add check if there's no new mentions or DMs in related reset-save functions --- js/twister_newmsgs.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/js/twister_newmsgs.js b/js/twister_newmsgs.js index dc147ff..6c03c72 100644 --- a/js/twister_newmsgs.js +++ b/js/twister_newmsgs.js @@ -128,6 +128,9 @@ function queryPendingPushMentions(req, res) { } function resetMentionsCount() { + if (!twister.mentions.lengthNew) + return; + twister.mentions.lengthNew = 0; for (var j in twister.mentions.twists.cached) @@ -420,30 +423,47 @@ function getNewGroupDMsCount() { } function resetNewDMsCount() { + var isNewDetected; + for (var peerAlias in twister.DMs) - if (peerAlias[0] !== '*') { + if (twister.DMs[peerAlias].lengthNew && peerAlias[0] !== '*') { twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; + + isNewDetected = true; } + if (!isNewDetected) + return; + saveDMsToStorage(); $.MAL.updateNewDMsUI(getNewDMsCount()); } function resetNewDMsCountGroup() { + var isNewDetected; + for (var peerAlias in twister.DMs) - if (peerAlias[0] === '*') { + if (twister.DMs[peerAlias].lengthNew && peerAlias[0] === '*') { twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; + + isNewDetected = true; } + if (!isNewDetected) + return; + saveDMsToStorage(); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); } function resetNewDMsCountForPeer(peerAlias) { + if (!twister.DMs[peerAlias].lengthNew) + return; + twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; From b89fafa61c6e61b4a8561ec8b2eeb981387b7549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=99=E6=83=9F=E5=80=AB?= Date: Tue, 8 Aug 2017 14:48:23 +0800 Subject: [PATCH 8/8] Localization for traditional Mandarin and Yue Chinese --- js/interface_localization.js | 760 ++++++++++++++++++++++++++++++++++- options.html | 2 + 2 files changed, 761 insertions(+), 1 deletion(-) diff --git a/js/interface_localization.js b/js/interface_localization.js index 945a05c..1a6530a 100644 --- a/js/interface_localization.js +++ b/js/interface_localization.js @@ -4,7 +4,7 @@ // uses Polyglot.js ( https://github.com/airbnb/polyglot.js ) to translate interface // translators: add your language code here such as "es" for Spanish, "ru" for Russian -var knownLanguages = ["en","es","nl","it","fr","ru","de","zh-CN","ja","pt-BR","tr","uk","cs"]; +var knownLanguages = ["en","es","nl","it","fr","ru","de","zh-CN","ja","pt-BR","tr","uk","cs","cmn","yue"]; if ($.Options.locLang.val === 'auto') { // detect language with JavaScript @@ -4806,6 +4806,764 @@ if(preferredLanguage == "cs"){ }; } +if(preferredLanguage == "cmn"){ + polyglot.locale("cmn"); + wordset = { + "Maximum post size to show": "可顯示的最長推文", + "Maximum post size to send": "可發送的最長推文", + "characters": "字元", + "WebTorrent": "網頁訊流", + "WebTorrent support to display shortened URL media": "網頁訊流支援顯示短版網址媒體", + "External IP:": "外部網路位址:", + "External Port 1 (TCP):": "外部通訊埠一(TCP):", + "External Port 2 (TCP+UDP):": "外部通訊埠二(TCP+UDP):", + "Test open port (external site)": "測試打開的通訊埠(外部站臺)", + "Actions ▼": "動作 ▼", + "Active DHT nodes:": "分散式雜湊表活躍節點:", + "Add DNS": "加入 DNS", + "Add peer": "加入對等點", + "ajax_error": "Ajax 錯誤:%{error}", // JavaScript error + "All users publicly followed by": "所有使用者被公開跟隨自", + "Available": "可用", // username is available + "Appearance": "外觀", + "Apply": "套用", + "Block chain information": "區塊鏈資訊", + "Block chain is up-to-date, twister is ready to use!": "區塊鏈已更新,Twister 備妥待用!", + "Block generation": "區塊生成:", + "busted_oh": "噢,不!", + "busted_avowal": "檢測到有人嘗試注入惡意材料", + "btn_ok": "沒問題", + "Cancel": "取消", + 'cant_get_requested_resourse': "無法於 %{link} 取得資源\n 狀態:%{status}。", + "clear_cache": "清空快取", + "Confirm": "確認", + "сonfirm_group_leaving_header": "確認要離開群組", + "сonfirm_group_leaving_body": "確定要離開 %{alias} 群組?", + "confirm_switch_to_network": + "本地守護程式還未連線到網路,或者區塊鏈已經過期。\n" + + "如果您待在這個頁面,您的動作可能沒有效用。\n" + + "您要檢查 [網路] 狀態頁面(%{page}) 做為替代嗎?", + "confirm_terminate_daemon": "確定要退離守護程式?\nTwister 客戶端將停止作用。", + "confirm_unfollow_@": "確定要取消跟隨 @%{alias}?", + "confirm_uri_shortener_clear_cache": "確定要清空瀏覽器的短版網址快取?", + "Change user": "變更使用者", + "Checking...": "檢查…", // checking if username is available + "Collapse": "塌縮", // smaller view of a post + "Configure block generation": "組配區塊生成", + "Connections:": "連接:", // to network + "Connection lost.": "連線已經中斷。", + "daemon_is_obsolete": "Twister 守護程式已過時,必須使用 %{versionReq} 或更高版本", + "days": "%{smart_count} 天", + "Detailed information": "詳細資訊", + "DHT network down.": "分散式雜湊表 網路斷線。", + "Direct Messages": "私人訊息", + "Group Messages": "群組訊息", + "Group Messages — New Group Creation": "群組訊息 — 新群組建立", + "Group Messages — Join Group": "群組訊息 — 參與群組", + "group_key_cant_import": "無法匯入私人訊息的群組金鑰", + "group_key_is_invalid_perhaps": "也許金鑰無效", + "group_key_was_imported": "已匯入私人訊息群組 %{alias} 的金鑰。\n" + +"很快就能擷取到它的訊息。", + "direct_messages_with": "與 %{username} 的私人訊息", + "Disable": "停用", + "display_mentions": "顯示提及次數", + "Display retransmissions": "顯示轉推次數", + "DNS to obtain list of peers:": "用來取得對等點列表的 DNS:", + "do_not_show_it_again": "再也不要顯示它", + "downloading_block_chain": "正在下載區塊鏈,繼續之前請稍待 (區塊鏈仍落後 %{days} 天)。", + "download_posts_status": "已下載 %{portion} 則推文", // Downloaded 10/30 posts + "Enable": "啟用", + "error": "錯誤:%{error}", + "error_connecting_to_daemon": "連線到本地 Twister 守護程式時發生錯誤。", + "Error in 'createwalletuser' RPC.": "在 createwalletuser RPC 時發生錯誤。", + "Error in 'importprivkey'": "在 importprivkey RPC 時發生錯誤:%{rpc}", + "Error in 'sendnewusertransaction' RPC.": "在 sendnewusertransaction RPC 時發生錯誤。", + "Expand": "展開", // larger view of a post + "Favorite": "收藏", + "File APIs not supported in this browser.": "這個瀏覽器不支援檔案應用程式介面。", + "Follow": "跟隨", + "Following config": "跟隨組配", + "select_way_to_follow_@": "您想以哪種方式跟隨 @%{alias}", + "Followed by": "跟隨者", + "followed_by": "被 %{username} 跟隨", + "Followers": "跟隨者", + "Followers_of": "@%{alias} 的跟隨者", + "Following": "跟隨中", + "Following users": "跟隨的使用者", + "Force connection to peer:": "強制連接到對等點:", + "General information": "一般資訊", + "Generate blocks (send promoted messages)": "產生區塊 (發送推廣訊息)", + "Home": "首頁", // homepage + "hours": "%{smart_count} 小時", + "Internal error: lastPostId unknown (following yourself may fix!)": "內部錯誤:lastPostId 不明 (跟隨您自己也許就能修正!)", + "Known peers:": "已知對等點:", + "Last block is ahead of your computer time, check your clock.": "最後一段區塊已經超前了您的電腦時間,請檢查您的系統時鐘。", + "mentions_at": "提及 @%{user}", + "minutes": "%{smart_count} 分鐘", + "Must be 16 characters or less.": "必須是 16 個字元或更少。", // username + "Network": "網路", + "Network config": "網路組配", + "Network status": "網路狀態", + "New direct message...": "新的私人訊息…", + "New Post...": "新推文…", + "New group": "新群組", + "Group description": "群組描述", + "Peers to invite": "可邀請的對等點", + "Join group": "參與群組", + "Select group(s)": "選取群組", + "Create": "建立", + "Join": "參與", + "Invite": "邀請", + "Invite peers": "邀請對等點", + "Leave group": "離開群組", + "You got": "您得到", + "in postboard": "於推布欄", + "in search result": "於搜尋結果", + "in top trends": "於熱門趨勢", + "new_posts": "%{smart_count} 則新推文", + "new_mentions": "%{smart_count} 次新提及", + "new_direct_messages": "%{smart_count} 則新的私人訊息", + "new_group_messages": "%{smart_count} 則新的群組訊息", + "nobody": "沒有人", // used to promote a post without attaching the user + "Not available": "無名可用", // username is not available + "warn_following_not_any": "沒有跟隨任何推友!\n請搜尋並跟隨某人。", + "warn_followers_not_all": "好吧,目前還沒有容易的方式,可以知道所有跟隨您的人。\n" + +"計數器衹有指示已知的對等點數量,他們正在共享您的推文訊流。\n" + +"以下列表包含的推友,大多是您所跟隨的人。", + "warn_followers_not_all_of": "好吧,目前還沒有容易的方式,可以知道某人的所有跟隨者。\n" + +"以下列表衹包括幾個,或許是公開跟隨 @%{alias} 的人。", + "notify_desktop_error": "Twister 無法進行桌面通知:發生不明錯誤。", + "notify_desktop_perm_denied": "Twister 無法進行桌面通知:權限被拒。\n\n如果您要獲得通知,請在您的瀏覽器設定值中,允許它們用於 %{this_domain}。", + "notify_desktop_test": "所有人都在推文。\n歡迎您的加入。", + "notify_desktop_title": "注意,這裡是 Twister!", + "post_preview_dummy": '這裡有 *粗體*、~斜體~、-刪除線- 以及 _加底線_ 的文字。\n' + + '同樣但是例外排除的:`*粗體*、~斜體~、-刪除線- 以及 _加底線_`。\n' + + '鏈結到 [最棒的圖標](%{logo}) 以及我們華麗的站臺:%{site}。', + "Number of blocks in block chain:": "區塊鏈中的區塊數量:", + "Number of CPUs to use": "使用的中央處理器數量:", + "Only alphanumeric and underscore allowed.": "衹允許文數字與底線。", + "peer address": "對等點位址", + "Private": "私人的", + "Profile": "側寫", + "Postboard": "推布欄", + "post": "推文", // verb - button to post a message + "Post to promote:": "用來推廣的推文:", + "Posts": "推文", + "propagating_nickname": "將暱稱 %{username} 傳播到網路…", + "Public": "公開", + "Refresh": "重新整理", + "retransmit_this": "轉發這則推文給您的跟隨者?", + "Reply": "回覆", + "Reply...": "回覆…", + "reply_to": "回覆給 %{fullname}", + "Retransmit": "轉發", + "Retransmits": "轉發", + "Retransmitted by": "轉發自", + "Switch to Reply": "切換為回覆", + "Switch to Retransmit": "切換為轉發", + "search": "搜尋", + "seconds": "%{smart_count} 秒", + "send": "發送", + "Send post with username": "發送推文所用的使用者名稱:", + "send_DM": "發送私人訊息", + "Sent Post to @": "已發送推文給 @", + "Setup account": "設定帳號", + "shorten_URI": "短版網址", + "shorten_URI_enter_link": "輸入長版網址鏈結。\n" + +"註記:短版網址將為您產生空的推文以包含完整網址。\n" + +"這一則特殊推文不會顯示於 Twister 客戶端,但是您的推文計數將會增加。", + "shorten_URI_its_public_is_it_ok": "您的鏈結將是公開可讀的!確定這樣可以嗎?", + "URI_shortener": "網址縮短器", + "The File APIs are not fully supported in this browser.": "這個瀏覽器並不完全支援檔案應用軟體介面。", + "time_ago": "%{time} 之前", // 5 minutes ago + "Time of the last block:": "最後一段區塊的時間:", + "Type message here": "在這裡輸入訊息", + "Unfollow": "取消跟隨", + "Update": "更新", + "Auto updating": "自動更新", + 'updates_are_available': '有可用更新', + 'updates_not_available': '沒有可用更新', + 'updates_check_client': '檢查有無客戶端軟體更新', + 'updates_repo_overview': '目前我們是基於 %{repo} 分支 %{branch} 上於\n %{date} 的定案 %{commit}\n' + +'但是原始碼的最前緣已經位於\n 定案 %{commitUpstream} 的 %{dateUpstream}。', + 'updates_checkout_diff': '簽出 [GitHub 上的差異](%{link}) 來瞭解變更了什麼。', + 'updates_checkout_diff_nfmt': '簽出 GitHub 上的差異來瞭解變更了什麼:\n %{link}', + 'updates_upstream_isnt_changed': '相應分支的原始碼儲存庫似乎沒有變更。', + "Updating status...": "更新狀態…", // status of block chain + 'new_account_briefing': '正在將新建立的帳號傳播到網路中。' + +'請不要關閉這個視窗,也許需要幾分鐘。\n\n' + +'您的密鑰是:*%{secretKey}*\n\n' + +'強烈建議您利用這段時間去儲存您的密鑰。' + +'印出來、做螢幕快照、使用您手機中的照相功能,或是將它寫在紙上。\n\n' + +'從不同的電腦使用這個帳號時將會需要密鑰。' + +'如果您一旦遺失密鑰,您的帳號將永遠被鎖住' + +'(註記:~這是開發中軟體,它也許會崩潰而造成資料漏失~)。\n\n' + +'請稍待。當您還沒唸完 ~decentralization~ 之前,就會顯示「確定」按鈕。', + "user_not_yet_accepted": "其他對等點尚未接受這位新使用者。\n"+ + "很不幸地,無法儲存側寫\n"+ + "或在這個狀態下發送任何推文。\n\n"+ + "請稍待幾分鐘再繼續。\n\n"+ + "「儲存變更」將在處理完成時\n"+ + "自動啟用。(我保證這是您\n"+ + "使用 Twister 之前的最後一次\n"+ + "等待)。\n\n"+ + "祕訣:趁這時選擇您的頭像!", + "users_mentions": "@%{username} 的提及次數", + "users_profile": "%{username} 的側寫", + "username_undefined": "未定義的使用者名稱,這是登入必要項目。", + "View": "檢視", + "View All": "檢視全部", + "Who to Follow": "可以跟隨誰", + "Your message was sent!": "您的訊息已發送!", + "twister login": "Twister 登入", + "Existing local users": "既有本地使用者", + "Or...": "或…", + "Create a new user": "建立新使用者", + "Login": "登入", + "Check availability": "檢查可用程度", + "Create this nickname": "建立這個暱稱", + "Type nickname here": "在這裡輸入暱稱", + "Import secret key": "匯入密鑰", + "52-characters secret": "52 個字元的密鑰", + "With nickname": "與暱稱", + "Import key": "匯入金鑰", + "Client Version:": "客戶端版本:", + "Mining difficulty:": "挖礦難度:", + "Block generation status": "區塊生成狀態", + "Current hash rate:": "目前雜湊比率:", + "Terminate Daemon:": "終止守護程式:", + "Exit": "退離", + "Save Changes": "儲存變更", + "profile_saved": "側寫資料已被儲存到分散式雜湊表。", + "profile_not_saved": "無法儲存側寫資料。", + "Secret key:": "密鑰:", + "You have to log in to post messages.": "您必須登入才能推送訊息。", + "You have to log in to post replies.": "您必須登入才能回覆訊息。", + "You have to log in to retransmit messages.": "您必須登入才能轉發訊息。", + "You have to log in to use direct messages.": "您必須登入才能使用私人訊息。", + "You have to log in to follow users.": "您必須登入才能跟隨使用者。", + "You are not following anyone because you are not logged in.": "您沒有跟隨任何人因為您還未登入。", + "You don't have any followers because you are not logged in.": "您沒有任何跟隨者因為您還未登入。", + "No one can mention you because you are not logged in.": "沒有人可以提及您因為您還未登入。", + "You don't have any profile because you are not logged in.": "您沒有任何側寫因為您還未登入。", + "Options": "選項", + "Switch to Promoted posts": "切換至推廣推文", + "Switch to Normal posts": "切換至一般推文", + "Use language": "使用語言", + "Ignore": "忽略", + "Ignore and clear out": "忽略並清空", + "Theme": "布景主題", + "Keys": "金鑰", + "Notifications": "通知", + "Desktop notifications": "桌面通知", + "Sound notifications": "聲音通知", + "Volume": "音量", + "Test": "測試", + "Send key": "發送金鑰", + "Posts display": "推文顯示", + "Post editor": "推文編輯器", + "Post preview": "推文預覽", + "Inline image preview": "內聯影像預覽", + "Display": "顯示", + "Line feeds": "送列符號", + "Markout": "註明標記", + "Supported punctuations:": "支援的標點符號:", + "Supported emotions:": "支援的表情符號:", + "Supported signs:": "支援的符號:", + "Supported fractions:": "支援的分數:", + "Automatic unicode conversion options": "萬國碼自動轉換選項", + "Convert punctuations to unicode": "轉換標點符號為萬國碼", + "Convert emotions codes to unicode symbols": "轉換表情符號編碼為萬國碼符號", + "Convert common signs to unicode": "轉換一般符號為萬國碼", + "Convert fractions to unicode": "轉換分數為萬國碼", + "Convert all": "轉換全部", + "Auto": "自動", + "Original": "原版", + "none": "無", + "Custom": "自訂", + "Mentions": "提及", + "Use proxy for image preview only": "代理伺服器衹用於影像預覽", + "Use external links behind a proxy": "使用代理伺服器後方的外部鏈結", + "There aren't any posts with this hashtag.": "沒有任何推文包含這個雜湊標籤。", + "Split only new post": "衹分割新的推文", + "Split all": "分割全部", + "Don't split": "不要分割", + "Split long posts": "分割長的推文", + "Posts that begin with mention": "以提及做為開頭的推文", + "Show all": "全部顯示", + "Show only if I am in": "衹顯示有我的推文", + "Show if it's between users I follow": "如果是我跟隨的使用者才顯示", + "Postboard displays": "推布欄顯示", + "RTs those are close to original twist": "與原始推文相近的轉推", + "Show if the original is older than": "衹顯示原始推文出現超過", + "hour(s)": "小時", + "second(s)": "秒", + "only positive numbers!": "衹允許正數!", + "Language filtering": "語言篩選", + "By blacklist": "依照黑名單", + "By whitelist": "依照白名單", + "Comma separated ISO 639-3 language codes": "逗號分隔的 ISO 639-3 語言編碼", + "Accuracy": "準確度", + "Simulation mode": "模擬模式", + "This post is treated by language filter": "這則推文由語言篩選器所 %{treated}。", + "blocked": "阻斷", + "passed": "傳遞", + "not analyzed": "無法分析", + "Reason: this": "原因:%{this}", + "this doesnt contain that": "%{this} 不包含 %{that}", + "this is undefined": "%{this} 未定義", + "blacklist": "黑名單", + "whitelist": "白名單", + "language of this": "語言所屬", + "its undefined language": "它是種未定義的語言", + "its this, blacklisted": "它是 %{this},已列入黑名單", + "its this, whitelisted": "它是 %{this},已列入白名單", + "Most possible language: this": "最可能的語言:%{this}", + "Scope of usage": "使用的範圍", + "Show with every user name": "每位使用者都要顯示", + "Show at profile modal only": "衹有側寫頁面才要顯示", + "Show if a user follows me": "跟隨我的使用者才顯示", + "follows you": "跟隨您", + "Show conversation": "顯示會話", + "Mark all as read": "標記所有為已讀", + "show_more_count": "%{smart_count} 更多…", + "hide": "隱藏", + "Show more in this conversation...": "顯示更多會話內容…", + "conversation_title": "@%{username} 的會話", + "copy_to_clipboard": "按下 Ctrl/Cmd+C 來拷貝,然後按下 Enter 來關閉", + "Normal posts": "一般推文", + "Promoted posts": "推廣推文", + "Messages": "訊息", + "Edit profile": "編輯側寫", + "Top Trends": "熱門趨勢", + "Twistday Reminder": "開推周年紀念日提醒訊息", + "Show upcoming in near future": "顯示不久即將迎來慶祝的人", + "Who's celebrating Twistday": "誰正在慶祝開推周年紀念日", + "Today's luckies:": "今天的幸運兒:", + "Upcoming ones:": "即將迎來慶祝的人:", + "post_rt_sign_prep": "再次推文自", + "post_rt_time_prep": "於", + "undo": "復原", + "Daemon exited...": "已退離守護程式…", + "Secret Key": "密鑰", + "Copy to clipboard": "拷貝到剪貼簿", + "Full name here": "全名", + "Describe yourself": "描述您自己", + "Location": "位置", + "website": "網站", + "Tox address": "Tox 位址", + "Bitmessage address": "Bitmessage 位址", + "Language": "語言", + "Sound": "聲音", + "Users": "使用者", + "Direct Message's copy to self": "私人訊息拷貝給自己", + "Terminate Daemon": "終止守護程式", + "New post": "新推文", + "Search": "搜尋", + "Direct Msg": "私人訊息", + "Traffic information": "流量資訊", + "DHT Torrents:": "分散式雜湊表訊流:", + "Peers:": "對等點:", + "Peer List Size:": "對等點列表大小:", + "Active Requests:": "活躍請求:", + "Download:": "下載:", + "Upload:": "上傳:", + "DHT Download:": "分散式雜湊表下載:", + "DHT Upload:": "分散式雜湊表上傳:", + "IP Overhead Download:": "網際網路協定耗費下載:", + "IP Overhead Upload:": "網際網路協定耗費上傳:", + "Payload Download:": "酬載下載:", + "Payload Upload:": "酬載上傳:", + "No favs here because you are not logged in.": "因為您還沒登入,這裡不會顯示收藏。", + "users_favs": "@%{username} 的收藏", + "Favorites": "收藏", + "You have to log in to favorite messages.": "您必須登入以收藏訊息。", + "fav_this": "它為您所獨有?", + "Last activity": "最近一次活動", + "New Users": "新近使用者", + "Live tracking" : "即時追蹤" + }; +} + +if(preferredLanguage == "yue"){ + polyglot.locale("yue"); + wordset = { + "Maximum post size to show": "可顯示其至長推文", + "Maximum post size to send": "可寄個其至長推文", + "characters": "字符", + "WebTorrent": "網頁訊流", + "WebTorrent support to display shortened URL media": "網頁訊流支援顯示短版網址媒體", + "External IP:": "外部網絡位址:", + "External Port 1 (TCP):": "外部通訊接口一(TCP):", + "External Port 2 (TCP+UDP):": "外部通訊接口二(TCP+UDP):", + "Test open port (external site)": "測試開其通訊接口(外部站台)", + "Actions ▼": "動作 ▼", + "Active DHT nodes:": "分散式雜湊表生猛節點:", + "Add DNS": "添加 DNS", + "Add peer": "添加對等點", + "ajax_error": "Ajax 錯誤:%{error}", // JavaScript error + "All users publicly followed by": "所有用家畀公開發摟自", + "Available": "可用", // username is available + "Appearance": "外觀", + "Apply": "改實", + "Block chain information": "區塊捵資訊", + "Block chain is up-to-date, twister is ready to use!": "區塊捵已更加新,Twister 備妥待用!", + "Block generation": "區塊生成:", + "busted_oh": "噢,毋!", + "busted_avowal": "檢測到有人嘗試斟惡意材料", + "btn_ok": "無問題", + "Cancel": "毋做", + 'cant_get_requested_resourse': "無法嚮 %{link} 攎資源\n 狀態:%{status}。", + "clear_cache": "清空快取", + "Confirm": "落實", + "сonfirm_group_leaving_header": "落實要行開谷", + "сonfirm_group_leaving_body": "直煞要行開 %{alias} 谷嘛?", + "confirm_switch_to_network": + "本地守護程式尚未連綫到網絡,定抑區塊捵經已過期。\n" + + "若果閣下待繫個頁面,閣下其動作似無效用。\n" + + "閣下要檢查 [網絡] 狀態頁面(%{page}) 做為幫代啊?", + "confirm_terminate_daemon": "直煞要退出守護程式嘛?\nTwister 客戶端將閘住作用。", + "confirm_unfollow_@": "直煞要毋做發摟 @%{alias}嘛?", + "confirm_uri_shortener_clear_cache": "直煞要清空瀏覽器其短版網址快取嘛?", + "Change user": "更改用家", + "Checking...": "檢查…", // checking if username is available + "Collapse": "塌縮", // smaller view of a post + "Configure block generation": "組襯區塊生成", + "Connections:": "接通:", // to network + "Connection lost.": "連綫經已掹開。", + "daemon_is_obsolete": "Twister 守護程式過着時,必須用 %{versionReq} 或更加高版本", + "days": "%{smart_count} 天", + "Detailed information": "詳細資訊", + "DHT network down.": "分散式雜湊表 網絡斷綫。", + "Direct Messages": "私人口訊", + "Group Messages": "谷口訊", + "Group Messages — New Group Creation": "谷口訊 — 新谷建立", + "Group Messages — Join Group": "谷口訊 — 埋份谷", + "group_key_cant_import": "無法匯入私人口訊其谷密碼匙", + "group_key_is_invalid_perhaps": "亦許密碼匙無效", + "group_key_was_imported": "已匯入私人口訊谷 %{alias} 其密碼匙。\n" + +"好快就能擷取到佢其口訊。", + "direct_messages_with": "同 %{username} 其私人口訊", + "Disable": "停用", + "display_mentions": "顯示提及次數", + "Display retransmissions": "顯示轉推次數", + "DNS to obtain list of peers:": "用來攎對等點列表其 DNS:", + "do_not_show_it_again": "再亦毋好顯示佢", + "downloading_block_chain": "取得緊區塊捵,繼續之前請稍待 (區塊捵仍落後 %{days} 天)。", + "download_posts_status": "已取得 %{portion} 則推文", // Downloaded 10/30 posts + "Enable": "啟用", + "error": "錯誤:%{error}", + "error_connecting_to_daemon": "連綫到本地 Twister 守護程式時發生錯誤。", + "Error in 'createwalletuser' RPC.": "繫 createwalletuser RPC 時發生錯誤。", + "Error in 'importprivkey'": "繫 importprivkey RPC 時發生錯誤:%{rpc}", + "Error in 'sendnewusertransaction' RPC.": "繫 sendnewusertransaction RPC 時發生錯誤。", + "Expand": "展開", // larger view of a post + "Favorite": "收匿", + "File APIs not supported in this browser.": "個瀏覽器毋支援快勞應用程式界面。", + "Follow": "發摟", + "Following config": "發摟組襯", + "select_way_to_follow_@": "閣下諗俾焉種方式發摟 @%{alias}", + "Followed by": "發摟者", + "followed_by": "畀 %{username} 發摟", + "Followers": "發摟者", + "Followers_of": "@%{alias} 其發摟者", + "Following": "發摟中", + "Following users": "發摟其用家", + "Force connection to peer:": "夾硬接通到對等點:", + "General information": "一般資訊", + "Generate blocks (send promoted messages)": "產生區塊 (寄個推廣口訊)", + "Home": "頭版", // homepage + "hours": "%{smart_count} 個鐘", + "Internal error: lastPostId unknown (following yourself may fix!)": "內部錯誤:lastPostId 未知 (發摟閣下自己亦許就能修正!)", + "Known peers:": "已知對等點:", + "Last block is ahead of your computer time, check your clock.": "最後一橛區塊經已超前着閣下其電腦時間,請檢查閣下其系統時鐘。", + "mentions_at": "提及 @%{user}", + "minutes": "%{smart_count} 分鐘", + "Must be 16 characters or less.": "必須係 16 個字符或更加少。", // username + "Network": "網絡", + "Network config": "網絡組襯", + "Network status": "網絡狀態", + "New direct message...": "新淨私人口訊…", + "New Post...": "新推文…", + "New group": "新谷", + "Group description": "谷斟", + "Peers to invite": "可邀請其對等點", + "Join group": "埋份谷", + "Select group(s)": "繫度揀谷", + "Create": "建立", + "Join": "埋份", + "Invite": "邀請", + "Invite peers": "邀請對等點", + "Leave group": "行開谷", + "You got": "閣下得到", + "in postboard": "嚮推布欄", + "in search result": "嚮揾結果", + "in top trends": "嚮熱門趨勢", + "new_posts": "%{smart_count} 則新推文", + "new_mentions": "%{smart_count} 次新提及", + "new_direct_messages": "%{smart_count} 則新淨私人口訊", + "new_group_messages": "%{smart_count} 則新淨谷口訊", + "nobody": "無人", // used to promote a post without attaching the user + "Not available": "無名可用", // username is not available + "warn_following_not_any": "無發摟任何推友!\n請揾兼發摟某人。", + "warn_followers_not_all": "好吧,而今尚未易其方式,得知所有發摟閣下其人。\n" + +"計數器惟有指示已知其對等點數量,佢地正係共享閣下其推文訊流。\n" + +"以下列表埋其推友,大多係閣下所發摟其人。", + "warn_followers_not_all_of": "好吧,而今尚未易其方式,得知某人其所有發摟者。\n" + +"以下列表衹埋幾個,一係係公開發摟 @%{alias} 其人。", + "notify_desktop_error": "Twister 無法去枱面通知:發生未知錯誤。", + "notify_desktop_perm_denied": "Twister 無法去枱面通知:權限畀拒。\n\n若果閣下要攎到通知,請繫閣下其瀏覽器設定值中,允許佢用嚮 %{this_domain}。", + "notify_desktop_test": "冚不論都繫推文。\n歡迎閣下其添加。", + "notify_desktop_title": "留意,呢度係 Twister!", + "post_preview_dummy": '呢度有 *粗字*、~斜字~、-刪清綫- 同埋 _加底綫_ 其文字。\n' + + '同樣但例外飛起其:`*粗字*、~斜字~、-刪清綫- 同埋 _加底綫_`。\n' + + '連結到 [一流其圖標](%{logo}) 同埋我地華麗其站台:%{site}。', + "Number of blocks in block chain:": "區塊捵中其區塊數量:", + "Number of CPUs to use": "用其中央處理器數量:", + "Only alphanumeric and underscore allowed.": "衹允許文數字同底綫。", + "peer address": "對等點位址", + "Private": "私人其", + "Profile": "個人資料", + "Postboard": "推布欄", + "post": "推文", // verb - button to post a message + "Post to promote:": "用來推廣其推文:", + "Posts": "推文", + "propagating_nickname": "將網名 %{username} 傳播到網絡…", + "Public": "公開", + "Refresh": "整返", + "retransmit_this": "轉發呢則推文畀閣下其發摟者嘛?", + "Reply": "覆返", + "Reply...": "覆返…", + "reply_to": "覆返畀 %{fullname}", + "Retransmit": "轉發", + "Retransmits": "轉發", + "Retransmitted by": "轉發自", + "Switch to Reply": "切換為覆返", + "Switch to Retransmit": "切換為轉發", + "search": "揾耶", + "seconds": "%{smart_count} 秒", + "send": "寄個", + "Send post with username": "寄個推文所用其用家名稱:", + "send_DM": "寄個私人口訊", + "Sent Post to @": "已寄個推文畀 @", + "Setup account": "設定戶口", + "shorten_URI": "短版網址", + "shorten_URI_enter_link": "輸入長版網址連結。\n" + +"註記:短版網址將為閣下產生空其推文俾埋齊煞網址。\n" + +"則特意推文毋會顯示嚮 Twister 客戶端,但閣下其推文計數會提返。", + "shorten_URI_its_public_is_it_ok": "閣下其連結將係公開可讀其!直煞敢得啩?", + "URI_shortener": "網址縮短器", + "The File APIs are not fully supported in this browser.": "個瀏覽器兼毋完全支援快勞應用軟件界面。", + "time_ago": "%{time} 之前", // 5 minutes ago + "Time of the last block:": "最後一橛區塊其時間:", + "Type message here": "繫度輸入口訊", + "Unfollow": "毋做發摟", + "Update": "更加新", + "Auto updating": "自動更加新", + 'updates_are_available': '有可用更加新 ', + 'updates_not_available': '無可用更加新 ', + 'updates_check_client': '檢查有無客戶端軟件更加新 ', + 'updates_repo_overview': '而今我地係跟埋 %{repo} 分支 %{branch} 上嚮\n %{date} 其定案 %{commit}\n' + +'但原始碼其至前緣經已位嚮\n 定案 %{commitUpstream} 其 %{dateUpstream}。', + 'updates_checkout_diff': '簽走 [GitHub 上其差異](%{link}) 來知更改着麼耶。', + 'updates_checkout_diff_nfmt': '簽走 GitHub 上其差異來知更改着麼:\n %{link}', + 'updates_upstream_isnt_changed': '相應分支其原始碼保存庫似乎無更改。', + "Updating status...": "更加新狀態…", // status of block chain + 'new_account_briefing': '正係將新建立其戶口傳播到網絡中。' + +'請毋好閂埋個視窗,亦許需要幾分鐘。\n\n' + +'閣下其密碼匙係:*%{secretKey}*\n\n' + +'強勍建議閣下利用呢段時間去保存閣下其密碼匙。' + +'顯示來、做熒幕快照、用閣下手提電話中其影相功能,定係將佢寫繫紙上。\n\n' + +'由毋同其電腦用個戶口時會需要密碼匙。' + +'若果閣下一旦遺失密碼匙,閣下其戶口將永遠畀鎖住 ' + +'(註記:~ 呢係開發中軟件,佢亦許會冧而做成資料漏失 ~)。\n\n' + +'請稍待。當閣下尚未讀煞 ~decentralization~ 之前,就會顯示「直煞」掣。', + "user_not_yet_accepted": "遞滴對等點未接受呢位新用家。\n"+ + "好毋幸地,無法保存個人資料\n"+ + "或繫個狀態下寄個任何推文。\n\n"+ + "請稍待幾分鐘再繼續。\n\n"+ + "「保存更改」將繫處理搞直時\n"+ + "自動啟用。(我包呢係閣下\n"+ + "用 Twister 之前其最後一次\n"+ + "等待)。\n\n"+ + "秘訣:趁呢時揀閣下其頭像!", + "users_mentions": "@%{username} 其提及次數", + "users_profile": "%{username} 其個人資料", + "username_undefined": "未定義其用家名稱,呢係登入要寫項目。", + "View": "查看", + "View All": "查看煞", + "Who to Follow": "得發摟焉位", + "Your message was sent!": "閣下其口訊已寄個!", + "twister login": "Twister 登入", + "Existing local users": "既有本地用家", + "Or...": "或…", + "Create a new user": "建立新用家", + "Login": "登入", + "Check availability": "檢查可用程度", + "Create this nickname": "建立個網名", + "Type nickname here": "繫度輸入網名", + "Import secret key": "匯入密碼匙", + "52-characters secret": "52 個字符其密碼匙", + "With nickname": "同網名", + "Import key": "匯入密碼匙", + "Client Version:": "客戶端版本:", + "Mining difficulty:": "掘礦難度:", + "Block generation status": "區塊生成狀態", + "Current hash rate:": "而今雜湊比率:", + "Terminate Daemon:": "終止守護程式:", + "Exit": "退出", + "Save Changes": "保存更改", + "profile_saved": "個人資料資料已畀保存到分散式雜湊表。", + "profile_not_saved": "無法保存個人資料資料。", + "Secret key:": "密碼匙:", + "You have to log in to post messages.": "閣下必須登入先能推送口訊。", + "You have to log in to post replies.": "閣下必須登入先能覆返口訊。", + "You have to log in to retransmit messages.": "閣下必須登入先能轉發口訊。", + "You have to log in to use direct messages.": "閣下必須登入先能用私人口訊。", + "You have to log in to follow users.": "閣下必須登入先能發摟用家。", + "You are not following anyone because you are not logged in.": "閣下無發摟任何人因為閣下尚未登入。", + "You don't have any followers because you are not logged in.": "閣下無發摟者因為閣下尚未登入。", + "No one can mention you because you are not logged in.": "無人得提及閣下因為閣下尚未登入。", + "You don't have any profile because you are not logged in.": "閣下無個人資料因為閣下尚未登入。", + "Options": "隨寫", + "Switch to Promoted posts": "切換至推廣推文", + "Switch to Normal posts": "切換至一般推文", + "Use language": "用語言", + "Ignore": "毋理", + "Ignore and clear out": "毋理兼清空", + "Theme": "布景主題", + "Keys": "密碼匙", + "Notifications": "通知", + "Desktop notifications": "枱面通知", + "Sound notifications": "聲音通知", + "Volume": "音量", + "Test": "測試", + "Send key": "寄個密碼匙", + "Posts display": "推文顯示", + "Post editor": "推文編輯器", + "Post preview": "推文預覽", + "Inline image preview": "內聯錄像預覽", + "Display": "顯示", + "Line feeds": "送列符號", + "Markout": "註明嘜頭", + "Supported punctuations:": "支援其標點符號:", + "Supported emotions:": "支援其表情符號:", + "Supported signs:": "支援其符號:", + "Supported fractions:": "支援其分數:", + "Automatic unicode conversion options": "萬國碼自動轉換隨寫", + "Convert punctuations to unicode": "轉換標點符號為萬國碼", + "Convert emotions codes to unicode symbols": "轉換表情符號編碼為萬國碼符號", + "Convert common signs to unicode": "轉換一般符號為萬國碼", + "Convert fractions to unicode": "轉換分數為萬國碼", + "Convert all": "轉換煞", + "Auto": "自動", + "Original": "原版", + "none": "無", + "Custom": "自訂", + "Mentions": "提及", + "Use proxy for image preview only": "代理伺服器衹用嚮錄像預覽", + "Use external links behind a proxy": "用代理伺服器後方其外部連結", + "There aren't any posts with this hashtag.": "無推文埋個雜湊標籤。", + "Split only new post": "衹斬開新淨推文", + "Split all": "斬開煞", + "Don't split": "毋好斬開", + "Split long posts": "斬開長其推文", + "Posts that begin with mention": "俾提及做為開頭其推文", + "Show all": "冚顯示", + "Show only if I am in": "衹顯示有我其推文", + "Show if it's between users I follow": "若果係我發摟其用家先顯示", + "Postboard displays": "推布欄顯示", + "RTs those are close to original twist": "同原始推文相近其轉推", + "Show if the original is older than": "衹顯示原始推文浮頭超過", + "hour(s)": "個鐘", + "second(s)": "秒", + "only positive numbers!": "衹允許正數!", + "Language filtering": "語言篩揀", + "By blacklist": "依照黑名單", + "By whitelist": "依照白名單", + "Comma separated ISO 639-3 language codes": "撩下號分隔其 ISO 639-3 語言編碼", + "Accuracy": "準確度", + "Simulation mode": "模擬模式", + "This post is treated by language filter": "呢則推文由語言篩揀器所 %{treated}。", + "blocked": "杯葛", + "passed": "傳遞", + "not analyzed": "無法分析", + "Reason: this": "因由:%{this}", + "this doesnt contain that": "%{this} 毋埋 %{that}", + "this is undefined": "%{this} 未定義", + "blacklist": "黑名單", + "whitelist": "白名單", + "language of this": "語言所屬", + "its undefined language": "佢係種未定義其語言", + "its this, blacklisted": "佢係 %{this},已列入黑名單", + "its this, whitelisted": "佢係 %{this},已列入白名單", + "Most possible language: this": "至似其語言:%{this}", + "Scope of usage": "用其範圍", + "Show with every user name": "每位用家都要顯示", + "Show at profile modal only": "惟有個人資料頁面先要顯示", + "Show if a user follows me": "發摟我其用家先顯示", + "follows you": "發摟閣下", + "Show conversation": "顯示會話", + "Mark all as read": "嘜頭所有為已讀", + "show_more_count": "%{smart_count} 更加多…", + "hide": "匿埋", + "Show more in this conversation...": "顯示更加多會話內容…", + "conversation_title": "@%{username} 其會話", + "copy_to_clipboard": "撳 Ctrl/Cmd+C 來複製,跟住撳 Enter 來閂埋", + "Normal posts": "一般推文", + "Promoted posts": "推廣推文", + "Messages": "口訊", + "Edit profile": "編輯個人資料", + "Top Trends": "熱門趨勢", + "Twistday Reminder": "開推周年紀念日提醒口訊", + "Show upcoming in near future": "顯示毋耐來緊迎來慶祝其人", + "Who's celebrating Twistday": "焉位正係慶祝開推周年紀念日", + "Today's luckies:": "今天其幸逳兒:", + "Upcoming ones:": "來緊迎來慶祝其人:", + "post_rt_sign_prep": "再次推文自", + "post_rt_time_prep": "嚮", + "undo": "揾返", + "Daemon exited...": "已退出守護程式…", + "Secret Key": "密碼匙", + "Copy to clipboard": "複製到剪貼簿", + "Full name here": "冚名", + "Describe yourself": "斟閣下自己", + "Location": "地步", + "website": "網站", + "Tox address": "Tox 位址", + "Bitmessage address": "Bitmessage 位址", + "Language": "語言", + "Sound": "聲音", + "Users": "用家", + "Direct Message's copy to self": "私人口訊複製畀自己", + "Terminate Daemon": "終止守護程式", + "New post": "新推文", + "Search": "揾耶", + "Direct Msg": "私人口訊", + "Traffic information": "流量資訊", + "DHT Torrents:": "分散式雜湊表訊流:", + "Peers:": "對等點:", + "Peer List Size:": "對等點列表大細:", + "Active Requests:": "生猛請求:", + "Download:": "取得:", + "Upload:": "上傳:", + "DHT Download:": "分散式雜湊表取得:", + "DHT Upload:": "分散式雜湊表上傳:", + "IP Overhead Download:": "互聯網協定耗費取得:", + "IP Overhead Upload:": "互聯網協定耗費上傳:", + "Payload Download:": "酬載取得:", + "Payload Upload:": "酬載上傳:", + "No favs here because you are not logged in.": "因為閣下尚未登入,呢度毋會顯示收匿。", + "users_favs": "@%{username} 其收匿", + "Favorites": "收匿", + "You have to log in to favorite messages.": "閣下必須登入俾收匿口訊。", + "fav_this": "佢為閣下所獨有嘛?", + "Last activity": "最近一次活動", + "New Users": "新近用家", + "Live tracking" : "即時追蹤" + }; +} + // uncomment to see all translated words replaced with filler //for(var word in wordset){ // wordset[word] = "AAAA"; diff --git a/options.html b/options.html index 1cc3a23..09b881d 100644 --- a/options.html +++ b/options.html @@ -81,10 +81,12 @@ + +