Improved admin translation interface.
This commit is contained in:
parent
423c7428a7
commit
436ddad323
7 changed files with 227 additions and 39 deletions
|
@ -1,4 +1,9 @@
|
||||||
class Index extends App.ControllerContent
|
class Index extends App.ControllerContent
|
||||||
|
events:
|
||||||
|
'click .js-pushChanges': 'pushChanges'
|
||||||
|
'click .js-resetChanges': 'resetChanges'
|
||||||
|
'click .js-syncChanges': 'syncChanges'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
|
@ -19,11 +24,11 @@ class Index extends App.ControllerContent
|
||||||
{ name: 'locale', display: '', tag: 'select', null: false, class: 'input', options: options, default: App.i18n.get() },
|
{ name: 'locale', display: '', tag: 'select', null: false, class: 'input', options: options, default: App.i18n.get() },
|
||||||
]
|
]
|
||||||
load = (params) =>
|
load = (params) =>
|
||||||
new TranslationToDo(
|
@translationToDo = new TranslationToDo(
|
||||||
el: @$('.js-ToDo')
|
el: @$('.js-ToDo')
|
||||||
locale: params.locale
|
locale: params.locale
|
||||||
)
|
)
|
||||||
new TranslationList(
|
@translationList = new TranslationList(
|
||||||
el: @$('.js-List')
|
el: @$('.js-List')
|
||||||
locale: params.locale
|
locale: params.locale
|
||||||
)
|
)
|
||||||
|
@ -38,12 +43,90 @@ class Index extends App.ControllerContent
|
||||||
release: =>
|
release: =>
|
||||||
rerender = ->
|
rerender = ->
|
||||||
App.Event.trigger('ui:rerender')
|
App.Event.trigger('ui:rerender')
|
||||||
|
if @translationToDo.changes() || @translationList.changes()
|
||||||
App.Delay.set(rerender, 400)
|
App.Delay.set(rerender, 400)
|
||||||
|
|
||||||
|
pushChanges: =>
|
||||||
|
locale = @$('[name="locale"]').val()
|
||||||
|
|
||||||
|
@modal = new App.ControllerModal(
|
||||||
|
head: 'Pushing own translations...'
|
||||||
|
message: 'Pushing own translations to i18n.zammad.com, Thanks for contributing!'
|
||||||
|
cancel: false
|
||||||
|
close: false
|
||||||
|
shown: true
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
@ajax(
|
||||||
|
id: 'translations'
|
||||||
|
type: 'PUT'
|
||||||
|
url: @apiPath + '/translations/push'
|
||||||
|
data: JSON.stringify(locale: locale)
|
||||||
|
processData: false
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@modal.hide()
|
||||||
|
error: =>
|
||||||
|
@modal.hide()
|
||||||
|
)
|
||||||
|
|
||||||
|
resetChanges: =>
|
||||||
|
locale = @$('[name="locale"]').val()
|
||||||
|
|
||||||
|
@modal = new App.ControllerModal(
|
||||||
|
head: 'Reseting changes...'
|
||||||
|
message: 'Reseting changes own translation changes...'
|
||||||
|
cancel: false
|
||||||
|
close: false
|
||||||
|
shown: true
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
@ajax(
|
||||||
|
id: 'translations'
|
||||||
|
type: 'POST'
|
||||||
|
url: @apiPath + '/translations/reset'
|
||||||
|
data: JSON.stringify(locale: locale)
|
||||||
|
processData: false
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
App.Event.trigger('i18n:translation_todo_reload')
|
||||||
|
App.Event.trigger('i18n:translation_list_reload')
|
||||||
|
@modal.hide()
|
||||||
|
error: =>
|
||||||
|
@modal.hide()
|
||||||
|
)
|
||||||
|
|
||||||
|
syncChanges: =>
|
||||||
|
locale = @$('[name="locale"]').val()
|
||||||
|
|
||||||
|
@modal = new App.ControllerModal(
|
||||||
|
head: 'Syncing with latest translations...'
|
||||||
|
message: 'Syncing with latest translations!'
|
||||||
|
cancel: false
|
||||||
|
close: false
|
||||||
|
shown: true
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
@ajax(
|
||||||
|
id: 'translations'
|
||||||
|
type: 'POST'
|
||||||
|
url: @apiPath + '/translations/sync'
|
||||||
|
data: JSON.stringify(locale: locale)
|
||||||
|
processData: false
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
App.Event.trigger('i18n:translation_todo_reload')
|
||||||
|
App.Event.trigger('i18n:translation_list_reload')
|
||||||
|
@modal.hide()
|
||||||
|
error: =>
|
||||||
|
@modal.hide()
|
||||||
|
)
|
||||||
|
|
||||||
class TranslationToDo extends App.Controller
|
class TranslationToDo extends App.Controller
|
||||||
|
hasChanges: false
|
||||||
events:
|
events:
|
||||||
'click .js-Create': 'create'
|
'click .js-create': 'create'
|
||||||
'click .js-TheSame': 'same'
|
'click .js-theSame': 'same'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@ -69,8 +152,16 @@ class TranslationToDo extends App.Controller
|
||||||
list: listNotTranslated
|
list: listNotTranslated
|
||||||
)
|
)
|
||||||
|
|
||||||
|
showAction: =>
|
||||||
|
@el.closest('.content').find('.js-changes').removeClass('hidden')
|
||||||
|
|
||||||
|
changes: =>
|
||||||
|
@hasChanges
|
||||||
|
|
||||||
create: (e) =>
|
create: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@hasChanges = true
|
||||||
|
@showAction()
|
||||||
field = $(e.target).closest('tr').find('.js-Item')
|
field = $(e.target).closest('tr').find('.js-Item')
|
||||||
source = field.data('source')
|
source = field.data('source')
|
||||||
target = field.val()
|
target = field.val()
|
||||||
|
@ -104,6 +195,8 @@ class TranslationToDo extends App.Controller
|
||||||
|
|
||||||
same: (e) =>
|
same: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@hasChanges = true
|
||||||
|
@showAction()
|
||||||
field = $(e.target).closest('tr').find('.js-Item')
|
field = $(e.target).closest('tr').find('.js-Item')
|
||||||
source = field.data('source')
|
source = field.data('source')
|
||||||
|
|
||||||
|
@ -135,6 +228,7 @@ class TranslationToDo extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
class TranslationList extends App.Controller
|
class TranslationList extends App.Controller
|
||||||
|
hasChanges: false
|
||||||
events:
|
events:
|
||||||
'blur .js-translated input': 'update'
|
'blur .js-translated input': 'update'
|
||||||
'click .js-translated .js-Reset': 'reset'
|
'click .js-translated .js-Reset': 'reset'
|
||||||
|
@ -159,10 +253,6 @@ class TranslationList extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
render: (data = {}) =>
|
render: (data = {}) =>
|
||||||
|
|
||||||
#if !App.i18n.notTranslatedFeatureEnabled(@locale)
|
|
||||||
# return
|
|
||||||
|
|
||||||
@strings = []
|
@strings = []
|
||||||
@times = []
|
@times = []
|
||||||
for item in data.list
|
for item in data.list
|
||||||
|
@ -176,13 +266,24 @@ class TranslationList extends App.Controller
|
||||||
strings: @strings
|
strings: @strings
|
||||||
)
|
)
|
||||||
ui = @
|
ui = @
|
||||||
|
changesAvailable = false
|
||||||
@$('.js-Item').each( (e) ->
|
@$('.js-Item').each( (e) ->
|
||||||
id = $(this).data('id')
|
id = $(this).data('id')
|
||||||
ui.updateRow(id)
|
ui.updateRow(id)
|
||||||
|
changesAvailable = true
|
||||||
)
|
)
|
||||||
|
if changesAvailable
|
||||||
|
@showAction()
|
||||||
|
|
||||||
|
showAction: =>
|
||||||
|
@el.closest('.content').find('.js-changes').removeClass('hidden')
|
||||||
|
|
||||||
|
changes: =>
|
||||||
|
@hasChanges
|
||||||
|
|
||||||
reset: (e) ->
|
reset: (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@hasChanges = true
|
||||||
field = $(e.target).closest('tr').find('.js-Item')
|
field = $(e.target).closest('tr').find('.js-Item')
|
||||||
id = field.data('id')
|
id = field.data('id')
|
||||||
source = field.data('source')
|
source = field.data('source')
|
||||||
|
@ -238,6 +339,7 @@ class TranslationList extends App.Controller
|
||||||
|
|
||||||
update: (e) ->
|
update: (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@hasChanges = true
|
||||||
id = $( e.target ).data('id')
|
id = $( e.target ).data('id')
|
||||||
source = $( e.target ).data('source')
|
source = $( e.target ).data('source')
|
||||||
format = $( e.target ).data('format')
|
format = $( e.target ).data('format')
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<h1><%- @T('Translations') %> <small></small></h1>
|
<h1><%- @T('Translations') %> <small></small></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-header-meta">
|
<div class="page-header-meta">
|
||||||
|
<a class="btn btn--success js-syncChanges"><%- @T('Sync with latest') %></a>
|
||||||
|
<a class="btn btn--danger hidden js-changes js-resetChanges"><%- @T('Reset Changes') %></a>
|
||||||
|
<a class="btn btn--primary hidden js-changes js-pushChanges"><%- @T('Push Changes') %></a>
|
||||||
<div class="language"></div>
|
<div class="language"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td title="<%= item[1] %>"><%= item[1] %></td>
|
<td title="<%= item[1] %>"><%= item[1] %></td>
|
||||||
<td class="translationOverview-itemContainer"><input class="js-Item translationOverview-item form-control" value="<%= item[2] %>" data-source="<%= item[1] %>"></td>
|
<td class="translationOverview-itemContainer"><input class="js-Item translationOverview-item form-control" value="<%= item[2] %>" data-source="<%= item[1] %>"></td>
|
||||||
<td><a href="#" class="js-Create"><%- @T('Create') %></a> / <a href="#" class="js-TheSame"><%- @T('is the same') %></a></td>
|
<td><a href="#" class="js-create"><%- @T('Create') %></a> / <a href="#" class="js-theSame"><%- @T('is the same') %></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -8,6 +8,31 @@ class TranslationsController < ApplicationController
|
||||||
render json: Translation.list( params[:locale] )
|
render json: Translation.list( params[:locale] )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# PUT /translations/push
|
||||||
|
def push
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
start = Time.zone.now
|
||||||
|
Translation.push(params[:locale])
|
||||||
|
if start > Time.zone.now - 5.seconds
|
||||||
|
sleep 4
|
||||||
|
end
|
||||||
|
render json: { message: 'ok' }, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /translations/sync
|
||||||
|
def sync
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
Translation.load
|
||||||
|
render json: { message: 'ok' }, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /translations/reset
|
||||||
|
def reset
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
Translation.reset(params[:locale])
|
||||||
|
render json: { message: 'ok' }, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
# GET /translations/admin/lang/:locale
|
# GET /translations/admin/lang/:locale
|
||||||
def admin
|
def admin
|
||||||
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
@ -16,11 +41,13 @@ class TranslationsController < ApplicationController
|
||||||
|
|
||||||
# GET /translations
|
# GET /translations
|
||||||
def index
|
def index
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
model_index_render(Translation, params)
|
model_index_render(Translation, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /translations/1
|
# GET /translations/1
|
||||||
def show
|
def show
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
model_show_render(Translation, params)
|
model_show_render(Translation, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -72,22 +72,54 @@ push translations to online
|
||||||
}
|
}
|
||||||
|
|
||||||
return true if translations_to_push.empty?
|
return true if translations_to_push.empty?
|
||||||
#return translations_to_push
|
|
||||||
url = 'https://i18n.zammad.com/api/v1/thanks_for_your_support'
|
url = 'https://i18n.zammad.com/api/v1/thanks_for_your_support'
|
||||||
|
|
||||||
|
translator_key = Setting.get('translator_key')
|
||||||
|
|
||||||
result = UserAgent.post(
|
result = UserAgent.post(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
locale: locale,
|
locale: locale,
|
||||||
translations: translations_to_push,
|
translations: translations_to_push,
|
||||||
fqdn: Setting.get('fqdn'),
|
fqdn: Setting.get('fqdn'),
|
||||||
translator_key: '',
|
translator_key: translator_key,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
json: true,
|
json: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
fail "Can't push translations to #{url}: #{result.error}" if !result.success?
|
fail "Can't push translations to #{url}: #{result.error}" if !result.success?
|
||||||
|
|
||||||
|
# set new translator_key if given
|
||||||
|
if result.data['translator_key']
|
||||||
|
translator_key = Setting.set('translator_key', result.data['translator_key'])
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
reset translations to origin
|
||||||
|
|
||||||
|
Translation.reset(locale)
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.reset(locale)
|
||||||
|
|
||||||
|
# only push changed translations
|
||||||
|
translations = Translation.where(locale: locale)
|
||||||
|
translations.each {|translation|
|
||||||
|
if !translation.target_initial || translation.target_initial.empty?
|
||||||
|
translation.destroy
|
||||||
|
elsif translation.target != translation.target_initial
|
||||||
|
translation.target = translation.target_initial
|
||||||
|
translation.save
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -101,43 +133,47 @@ get list of translations
|
||||||
|
|
||||||
def self.list(locale, admin = false)
|
def self.list(locale, admin = false)
|
||||||
|
|
||||||
# check cache
|
# use cache if not admin page is requested
|
||||||
if !admin
|
if !admin
|
||||||
list = cache_get( locale )
|
data = cache_get(locale)
|
||||||
end
|
end
|
||||||
if !list
|
if !data
|
||||||
|
|
||||||
|
# show total translations as reference count
|
||||||
|
data = {
|
||||||
|
'total' => Translation.where(locale: 'de-de').count,
|
||||||
|
}
|
||||||
list = []
|
list = []
|
||||||
translations = Translation.where( locale: locale.downcase ).order( :source )
|
translations = Translation.where(locale: locale.downcase).order(:source)
|
||||||
translations.each { |item|
|
translations.each { |item|
|
||||||
if admin
|
if admin
|
||||||
data = [
|
translation_item = [
|
||||||
item.id,
|
item.id,
|
||||||
item.source,
|
item.source,
|
||||||
item.target,
|
item.target,
|
||||||
item.target_initial,
|
item.target_initial,
|
||||||
item.format,
|
item.format,
|
||||||
]
|
]
|
||||||
list.push data
|
list.push translation_item
|
||||||
else
|
else
|
||||||
data = [
|
translation_item = [
|
||||||
item.id,
|
item.id,
|
||||||
item.source,
|
item.source,
|
||||||
item.target,
|
item.target,
|
||||||
item.format,
|
item.format,
|
||||||
]
|
]
|
||||||
list.push data
|
list.push translation_item
|
||||||
end
|
end
|
||||||
|
data['list'] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
# set cache
|
# set cache
|
||||||
if !admin
|
if !admin
|
||||||
cache_set( locale, list )
|
cache_set(locale, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
data
|
||||||
list: list,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
@ -174,12 +210,12 @@ translate strings in ruby context, e. g. for notifications
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_clear
|
def cache_clear
|
||||||
Cache.delete( 'Translation::' + locale.downcase )
|
Cache.delete( 'TranslationMap::' + locale.downcase )
|
||||||
end
|
end
|
||||||
def self.cache_set(locale, data)
|
def self.cache_set(locale, data)
|
||||||
Cache.write( 'Translation::' + locale.downcase, data )
|
Cache.write( 'TranslationMap::' + locale.downcase, data )
|
||||||
end
|
end
|
||||||
def self.cache_get(locale)
|
def self.cache_get(locale)
|
||||||
Cache.get( 'Translation::' + locale.downcase )
|
Cache.get( 'TranslationMap::' + locale.downcase )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
Zammad::Application.routes.draw do
|
Zammad::Application.routes.draw do
|
||||||
api_path = Rails.configuration.api_path
|
api_path = Rails.configuration.api_path
|
||||||
|
|
||||||
|
match api_path + '/translations/push', to: 'translations#push', via: :put
|
||||||
|
match api_path + '/translations/sync', to: 'translations#sync', via: :post
|
||||||
|
match api_path + '/translations/reset', to: 'translations#reset', via: :post
|
||||||
|
match api_path + '/translations/lang/:locale', to: 'translations#load', via: :get
|
||||||
|
match api_path + '/translations/admin/lang/:locale', to: 'translations#admin', via: :get
|
||||||
|
|
||||||
match api_path + '/translations', to: 'translations#index', via: :get
|
match api_path + '/translations', to: 'translations#index', via: :get
|
||||||
match api_path + '/translations/:id', to: 'translations#show', via: :get
|
match api_path + '/translations/:id', to: 'translations#show', via: :get
|
||||||
match api_path + '/translations', to: 'translations#create', via: :post
|
match api_path + '/translations', to: 'translations#create', via: :post
|
||||||
match api_path + '/translations/:id', to: 'translations#update', via: :put
|
match api_path + '/translations/:id', to: 'translations#update', via: :put
|
||||||
match api_path + '/translations/:id', to: 'translations#destroy', via: :delete
|
match api_path + '/translations/:id', to: 'translations#destroy', via: :delete
|
||||||
|
|
||||||
match api_path + '/translations/lang/:locale', to: 'translations#load', via: :get
|
|
||||||
match api_path + '/translations/admin/lang/:locale', to: 'translations#admin', via: :get
|
|
||||||
end
|
end
|
||||||
|
|
16
db/migrate/20150628000001_translator_key_add.rb
Normal file
16
db/migrate/20150628000001_translator_key_add.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class TranslatorKeyAdd < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Define translator identifier.',
|
||||||
|
name: 'translator_key',
|
||||||
|
area: 'i18n::translator_key',
|
||||||
|
description: 'Defines the translator identifier for contributions.',
|
||||||
|
options: {},
|
||||||
|
state: '',
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue