Some html and markup improvements for inline translation feature.

This commit is contained in:
Martin Edenhofer 2015-11-19 15:48:48 +01:00
parent d8f5046400
commit 2c93b1c57d
10 changed files with 112 additions and 114 deletions

View file

@ -29,36 +29,39 @@ class App.CustomerChat extends App.Controller
active_agents: 0 active_agents: 0
@render() @render()
@on 'layout-has-changed', @propagateLayoutChange @on 'layout-has-changed', @propagateLayoutChange
# update navbar on new status # update navbar on new status
@bind( @bind('chat_status_agent', (data) =>
'chat_status_agent' @meta = data
(data) => @updateMeta()
@meta = data if !@pushStateStarted
@updateMeta() @pushStateStarted = true
if !@pushStateStarted @interval(@pushState, 30000, 'pushState')
@pushStateStarted = true
@interval(@pushState, 30000, 'pushState')
) )
# add new chat window # add new chat window
@bind( @bind('chat_session_start', (data) =>
'chat_session_start' if data.session
(data) => @addChat(data.session)
if data.session
@addChat(data.session)
) )
# on new login or on # on new login or on
@bind( @bind('ws:login chat_agent_state', ->
'ws:login chat_agent_state' App.WebSocket.send(event:'chat_status_agent')
->
App.WebSocket.send(event:'chat_status_agent')
) )
App.WebSocket.send(event:'chat_status_agent') App.WebSocket.send(event:'chat_status_agent')
# rerender view, e. g. on langauge change
@bind('ui:rerender', =>
return if !@authenticate(true)
for session_id, chat of @chatWindows
chat.el.remove()
@chatWindows = {}
@render()
App.WebSocket.send(event:'chat_status_agent')
)
pushState: => pushState: =>
App.WebSocket.send( App.WebSocket.send(
event:'chat_agent_state' event:'chat_agent_state'
@ -212,27 +215,21 @@ class chatWindow extends App.Controller
@on 'layout-change', @scrollToBottom @on 'layout-change', @scrollToBottom
@bind( @bind('chat_session_typing', (data) =>
'chat_session_typing' return if data.session_id isnt @session.session_id
(data) => return if data.self_written
return if data.session_id isnt @session.session_id @showWritingLoader()
return if data.self_written
@showWritingLoader()
) )
@bind( @bind('chat_session_message', (data) =>
'chat_session_message' return if data.session_id isnt @session.session_id
(data) => return if data.self_written
return if data.session_id isnt @session.session_id @receiveMessage(data.message.content)
return if data.self_written
@receiveMessage(data.message.content)
) )
@bind( @bind('chat_session_left chat_session_closed', (data) =>
'chat_session_left chat_session_closed' return if data.session_id isnt @session.session_id
(data) => return if data.self_written
return if data.session_id isnt @session.session_id @addStatusMessage("<strong>#{data.realname}</strong> has left the conversation")
return if data.self_written @goOffline()
@addStatusMessage("<strong>#{data.realname}</strong> has left the conversation")
@goOffline()
) )
render: -> render: ->
@ -401,7 +398,7 @@ class chatWindow extends App.Controller
timestamp = Date.now() timestamp = Date.now()
if !@lastTimestamp or timestamp - @lastTimestamp > @showTimeEveryXMinutes * 60000 if !@lastTimestamp or timestamp - @lastTimestamp > @showTimeEveryXMinutes * 60000
label = App.i18n.translateContent('today') label = App.i18n.translateInline('today')
time = new Date().toTimeString().substr(0,5) time = new Date().toTimeString().substr(0,5)
if @lastAddedType is 'timestamp' if @lastAddedType is 'timestamp'
# update last time # update last time

View file

@ -2,48 +2,48 @@
class App.i18n class App.i18n
_instance = undefined _instance = undefined
@init: ( args ) -> @init: (args) ->
_instance ?= new _i18nSingleton( args ) _instance ?= new _i18nSingleton(args)
@translateContent: ( string, args... ) -> @translateContent: (string, args...) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.translateContent( string, args ) _instance.translateContent(string, args)
@translatePlain: ( string, args... ) -> @translatePlain: (string, args...) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.translatePlain( string, args ) _instance.translatePlain(string, args)
@translateInline: ( string, args... ) -> @translateInline: (string, args...) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.translateInline( string, args ) _instance.translateInline(string, args)
@translateTimestamp: ( args, offset = 0 ) -> @translateTimestamp: (args, offset = 0) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.timestamp( args, offset ) _instance.timestamp(args, offset)
@translateDate: ( args, offset = 0 ) -> @translateDate: (args, offset = 0) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.date( args, offset ) _instance.date(args, offset)
@get: -> @get: ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.get() _instance.get()
@set: ( args ) -> @set: (args) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.set( args ) _instance.set(args)
@setMap: (source, target, format) -> @setMap: (source, target, format) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.setMap( source, target, format ) _instance.setMap(source, target, format)
@meta: (source, target, format) -> @meta: (source, target, format) ->
if _instance == undefined if _instance == undefined
@ -53,22 +53,22 @@ class App.i18n
@notTranslatedFeatureEnabled: (locale) -> @notTranslatedFeatureEnabled: (locale) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.notTranslatedFeatureEnabled( locale ) _instance.notTranslatedFeatureEnabled(locale)
@getNotTranslated: (locale) -> @getNotTranslated: (locale) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.getNotTranslated( locale ) _instance.getNotTranslated(locale)
@removeNotTranslated: (locale, key) -> @removeNotTranslated: (locale, key) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.removeNotTranslated( locale, key ) _instance.removeNotTranslated(locale, key)
@setNotTranslated: (locale, key) -> @setNotTranslated: (locale, key) ->
if _instance == undefined if _instance == undefined
_instance ?= new _i18nSingleton() _instance ?= new _i18nSingleton()
_instance.setNotTranslated( locale, key ) _instance.setNotTranslated(locale, key)
@timeFormat: (locale, key) -> @timeFormat: (locale, key) ->
if _instance == undefined if _instance == undefined
@ -78,7 +78,7 @@ class App.i18n
class _i18nSingleton extends Spine.Module class _i18nSingleton extends Spine.Module
@include App.LogInclude @include App.LogInclude
constructor: ( locale ) -> constructor: (locale) ->
@mapTime = {} @mapTime = {}
@mapString = {} @mapString = {}
@mapMeta = @mapMeta =
@ -95,10 +95,9 @@ class _i18nSingleton extends Spine.Module
$this = $(e.target) $this = $(e.target)
$this.data 'before', $this.html() $this.data 'before', $this.html()
return $this return $this
# .delegate '.translation', 'blur keyup paste', (e) =>
.delegate '.translation', 'blur', (e) => .delegate '.translation', 'blur', (e) =>
$this = $(e.target) $this = $(e.target)
source = $this.attr('data-text') source = $this.attr('title')
# get new translation # get new translation
translation_new = $this.html() translation_new = $this.html()
@ -114,22 +113,22 @@ class _i18nSingleton extends Spine.Module
$this.data 'before', translation_new $this.data 'before', translation_new
# update runtime translation mapString # update runtime translation mapString
@mapString[ source ] = translation_new @mapString[source] = translation_new
# replace rest in page # replace rest in page
$(".translation[data-text='#{source}']").html( translation_new ) $(".translation[title='#{source}']").html(translation_new)
# update permanent translation mapString # update permanent translation mapString
translation = App.Translation.findByAttribute( 'source', source ) translation = App.Translation.findByAttribute('source', source)
if translation if translation
translation.updateAttribute( 'target', translation_new ) translation.updateAttribute('target', translation_new)
else else
translation = new App.Translation translation = new App.Translation
translation.load( translation.load(
locale: @locale, locale: @locale
source: source, source: source
target: translation_new, target: translation_new
) )
translation.save() translation.save()
return $this return $this
@ -137,7 +136,7 @@ class _i18nSingleton extends Spine.Module
get: -> get: ->
@locale @locale
set: ( localeToSet ) -> set: (localeToSet) ->
# prepare locale # prepare locale
localeToSet = localeToSet.toLowerCase() localeToSet = localeToSet.toLowerCase()
@ -182,7 +181,7 @@ class _i18nSingleton extends Spine.Module
@_notTranslatedLog = @notTranslatedFeatureEnabled(@locale) @_notTranslatedLog = @notTranslatedFeatureEnabled(@locale)
# set lang attribute of html tag # set lang attribute of html tag
$('html').prop( 'lang', @locale.substr(0, 2) ) $('html').prop('lang', @locale.substr(0, 2) )
@mapString = {} @mapString = {}
App.Ajax.request( App.Ajax.request(
@ -215,29 +214,24 @@ class _i18nSingleton extends Spine.Module
# load in collection if needed # load in collection if needed
if !_.isEmpty(mapToLoad) if !_.isEmpty(mapToLoad)
App.Translation.refresh( mapToLoad, {clear: true} ) App.Translation.refresh(mapToLoad, {clear: true} )
App.Event.trigger('i18n:language:change') App.Event.trigger('i18n:language:change')
) )
translateInline: ( string, args ) => translateInline: (string, args) =>
App.Utils.htmlEscape( @translate( string, args ) ) App.Utils.htmlEscape(@translate(string, args))
translateContent: ( string, args ) => translateContent: (string, args) =>
translated = App.Utils.htmlEscape( @translate( string, args ) ) if App.Config.get('translation_inline')
# replace = '<span class="translation" contenteditable="true" data-text="' + App.Utils.htmlEscape(string) + '">' + translated + '<span class="icon-edit"></span>' return '<span class="translation" contenteditable="true" title="' + App.Utils.htmlEscape(string) + '">' + App.Utils.htmlEscape(@translate(string)) + '</span>'
if App.Config.get( 'translation_inline' )
replace = '<span class="translation" contenteditable="true" data-text="' + App.Utils.htmlEscape(string) + '">' + translated + ''
# if !@_translated
# replace += '<span class="missing">XX</span>'
replace += '</span>'
else
translated
translatePlain: ( string, args ) => translated = App.Utils.htmlEscape(@translate(string, args))
@translate( string, args )
translate: ( string, args ) => translatePlain: (string, args) =>
@translate(string, args)
translate: (string, args) =>
# type convertation # type convertation
if typeof string isnt 'string' if typeof string isnt 'string'
@ -265,18 +259,19 @@ class _i18nSingleton extends Spine.Module
@_notTranslated[@locale][string] = true @_notTranslated[@locale][string] = true
# search %s # search %s
for arg in args if args
translated = translated.replace(/%s/, arg) for arg in args
translated = translated.replace(/%s/, arg)
@log 'debug', 'translate', string, args, translated @log 'debug', 'translate', string, args, translated
# return translated string # return translated string
return translated translated
meta: => meta: =>
@mapMeta @mapMeta
setMap: ( source, target, format = 'string' ) => setMap: (source, target, format = 'string') =>
if format is 'time' if format is 'time'
@mapTime[source] = target @mapTime[source] = target
else else
@ -287,23 +282,23 @@ class _i18nSingleton extends Spine.Module
return false return false
true true
getNotTranslated: ( locale ) => getNotTranslated: (locale) =>
@_notTranslated[locale || @locale] @_notTranslated[locale || @locale]
removeNotTranslated: ( locale, key ) => removeNotTranslated: (locale, key) =>
delete @_notTranslated[locale][key] delete @_notTranslated[locale][key]
setNotTranslated: ( locale, key ) => setNotTranslated: (locale, key) =>
@_notTranslated[locale][key] = true @_notTranslated[locale][key] = true
date: ( time, offset ) => date: (time, offset) =>
@convert(time, offset, @mapTime['date'] || @dateFormat) @convert(time, offset, @mapTime['date'] || @dateFormat)
timestamp: ( time, offset ) => timestamp: (time, offset) =>
@convert(time, offset, @mapTime['timestamp'] || @timestampFormat) @convert(time, offset, @mapTime['timestamp'] || @timestampFormat)
convert: ( time, offset, format ) -> convert: (time, offset, format) ->
s = ( num, digits ) -> s = (num, digits) ->
while num.toString().length < digits while num.toString().length < digits
num = '0' + num num = '0' + num
num num
@ -312,7 +307,7 @@ class _i18nSingleton extends Spine.Module
# add timezone diff, needed for unit tests # add timezone diff, needed for unit tests
if offset if offset
timeObject = new Date( timeObject.getTime() + (timeObject.getTimezoneOffset() * 60000) ) timeObject = new Date(timeObject.getTime() + (timeObject.getTimezoneOffset() * 60000))
d = timeObject.getDate() d = timeObject.getDate()
m = timeObject.getMonth() + 1 m = timeObject.getMonth() + 1
@ -320,12 +315,12 @@ class _i18nSingleton extends Spine.Module
S = timeObject.getSeconds() S = timeObject.getSeconds()
M = timeObject.getMinutes() M = timeObject.getMinutes()
H = timeObject.getHours() H = timeObject.getHours()
format = format.replace /dd/, s( d, 2 ) format = format.replace /dd/, s(d, 2)
format = format.replace /d/, d format = format.replace /d/, d
format = format.replace /mm/, s( m, 2 ) format = format.replace /mm/, s(m, 2)
format = format.replace /m/, m format = format.replace /m/, m
format = format.replace /yyyy/, y format = format.replace /yyyy/, y
format = format.replace /SS/, s( S, 2 ) format = format.replace /SS/, s(S, 2)
format = format.replace /MM/, s( M, 2 ) format = format.replace /MM/, s(M, 2)
format = format.replace /HH/, s( H, 2 ) format = format.replace /HH/, s(H, 2)
return format format

View file

@ -41,9 +41,9 @@ class App.PrettyDate
day = App.i18n.translateInline('d') day = App.i18n.translateInline('d')
if long if long
if unit > 1 || unit is 0 if unit > 1 || unit is 0
day = App.i18n.translateContent('days') day = App.i18n.translateInline('days')
else else
day = App.i18n.translateContent('day') day = App.i18n.translateInline('day')
string = unit + ' ' + day string = unit + ' ' + day
diff = diff - ( unit * 86400 ) diff = diff - ( unit * 86400 )
if unit >= 9 || diff < 3600 || count is 2 if unit >= 9 || diff < 3600 || count is 2
@ -83,9 +83,9 @@ class App.PrettyDate
minute = App.i18n.translateInline('m') minute = App.i18n.translateInline('m')
if long if long
if unit > 1 || unit is 0 if unit > 1 || unit is 0
minute = App.i18n.translateContent('minutes') minute = App.i18n.translateInline('minutes')
else else
minute = App.i18n.translateContent('minute') minute = App.i18n.translateInline('minute')
if string isnt '' if string isnt ''
string = string + ' ' string = string + ' '
string = string + unit + ' ' + minute string = string + unit + ' ' + minute

View file

@ -93,6 +93,8 @@ class App.Utils
# htmlEscaped = App.Utils.htmlEscape( rawText ) # htmlEscaped = App.Utils.htmlEscape( rawText )
@htmlEscape: ( ascii ) -> @htmlEscape: ( ascii ) ->
return ascii if !ascii
return ascii if !ascii.replace
ascii.replace(/&/g, '&amp;') ascii.replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')

View file

@ -16,5 +16,5 @@
</div> </div>
<div class="chat-controls"> <div class="chat-controls">
<div class="chat-input form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div> <div class="chat-input form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div>
<div class="btn btn--primary btn--slim btn--small js-send"><%= @T('Send') %></div> <div class="btn btn--primary btn--slim btn--small js-send"><%- @T('Send') %></div>
</div> </div>

View file

@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="page-header-meta"> <div class="page-header-meta">
<div class="btn btn--action" data-type="settings"><%= @T('Settings') %></div> <div class="btn btn--action" data-type="settings"><%- @T('Settings') %></div>
</div> </div>
</div> </div>
<div class="chat-workspace"></div> <div class="chat-workspace"></div>

View file

@ -30,8 +30,8 @@
<div class="stats-row email-channel"> <div class="stats-row email-channel">
<%- @Icon(channel.icon, 'stat-channel-icon') %> <%- @Icon(channel.icon, 'stat-channel-icon') %>
<div class="stat-bars"> <div class="stat-bars">
<div class="stat-bar primary" style="height: <%- channel.inbound_in_percent %>%" title="<%- @T('Inbound') %>: <%- channel.inbound_in_percent %>% (<%- channel.inbound %>)"></div> <div class="stat-bar primary" style="height: <%- channel.inbound_in_percent %>%" title="<%- @Ti('Inbound') %>: <%- channel.inbound_in_percent %>% (<%- channel.inbound %>)"></div>
<div class="stat-bar secondary" style="height: <%- channel.outbound_in_percent %>%" title="<%- @T('Outbound') %>: <%- channel.outbound_in_percent %>% (<%- channel.outbound %>)"></div> <div class="stat-bar secondary" style="height: <%- channel.outbound_in_percent %>%" title="<%- @Ti('Outbound') %>: <%- channel.outbound_in_percent %>% (<%- channel.outbound %>)"></div>
</div> </div>
<div class="stat-label"></div> <div class="stat-label"></div>
</div> </div>

View file

@ -1,6 +1,6 @@
<% for item in @item_list: %> <% for item in @item_list: %>
<a href="<%- item.data.url %>" title="<%= item.data.title %>" class="nav-tab task <%= item.data.class %><% if item.task.active: %> is-active<% end %><% if item.task.notify: %> is-modified<% end %>" data-key="<%- item.task.key %>"> <a href="<%- item.data.url %>" title="<%= item.data.title %>" class="nav-tab task <%= item.data.class %><% if item.task.active: %> is-active<% end %><% if item.task.notify: %> is-modified<% end %>" data-key="<%- item.task.key %>">
<div class="nav-tab-icon" title="<%- @T(item.data.iconTitle) %>"> <div class="nav-tab-icon" title="<%- @Ti(item.data.iconTitle) %>">
<% if item.data.type is 'task': %> <% if item.data.type is 'task': %>
<% if item.task.notify: %> <% if item.task.notify: %>
<%- @Icon('status-modified-inner-circle', "icon-task-state #{item.data.iconClass}") %> <%- @Icon('status-modified-inner-circle', "icon-task-state #{item.data.iconClass}") %>

View file

@ -1 +1 @@
<div contenteditable="true" class="ticket-title-update" data-placeholder="<%= @Ti('Enter Title...') %>"><%= @ticket.title %></div> <div contenteditable="true" class="ticket-title-update" data-placeholder="<%- @Ti('Enter Title...') %>"><%= @ticket.title %></div>

View file

@ -2564,6 +2564,10 @@ footer {
.translation[contenteditable="true"] { .translation[contenteditable="true"] {
display: inline; display: inline;
} }
.translation[contenteditable="true"]:hover,
.translation[contenteditable="true"]:focus {
background: none;
}
.translation .icon-edit { .translation .icon-edit {
display: none; display: none;
} }