diff --git a/Changes.md b/Changes.md
index b5d10599..b35c79a3 100644
--- a/Changes.md
+++ b/Changes.md
@@ -1,3 +1,11 @@
+2.10.0
+-----------
+
+- I18n for web UI. Please submit translations of `web/locales/en.yml` for
+your own language. [#811]
+- 'sinatra', 'slim' and 'i18n' are now gem dependencies for Sidekiq.
+
+
2.9.0
-----------
diff --git a/lib/sidekiq/version.rb b/lib/sidekiq/version.rb
index 81499b20..ef471443 100644
--- a/lib/sidekiq/version.rb
+++ b/lib/sidekiq/version.rb
@@ -1,3 +1,3 @@
module Sidekiq
- VERSION = "2.9.0"
+ VERSION = "2.10.0"
end
diff --git a/lib/sidekiq/web.rb b/lib/sidekiq/web.rb
index 442b6853..a9db8776 100644
--- a/lib/sidekiq/web.rb
+++ b/lib/sidekiq/web.rb
@@ -1,18 +1,28 @@
require 'sinatra/base'
require 'slim'
require 'sidekiq/paginator'
+require 'i18n'
module Sidekiq
class Web < Sinatra::Base
include Sidekiq::Paginator
dir = File.expand_path(File.dirname(__FILE__) + "/../../web")
+ I18n.load_path += Dir[File.join(dir, 'locales', '*.yml').to_s]
+
set :public_folder, "#{dir}/assets"
set :views, "#{dir}/views"
set :root, "#{dir}/public"
set :slim, :pretty => true
helpers do
+ def get_locale
+ (request.env["HTTP_ACCEPT_LANGUAGE"] || 'en')[0,2]
+ end
+
+ def t(msg, options={})
+ I18n.t(msg, options.merge(:locale => get_locale))
+ end
def reset_worker_list
Sidekiq.redis do |conn|
diff --git a/sidekiq.gemspec b/sidekiq.gemspec
index 7b2ab584..f9092a8f 100644
--- a/sidekiq.gemspec
+++ b/sidekiq.gemspec
@@ -19,9 +19,10 @@ Gem::Specification.new do |gem|
gem.add_dependency 'connection_pool', '~> 1.0'
gem.add_dependency 'celluloid', '~> 0.12.0'
gem.add_dependency 'multi_json', '~> 1'
+ gem.add_dependency 'sinatra'
+ gem.add_dependency 'slim'
+ gem.add_dependency 'i18n'
gem.add_development_dependency 'minitest', '~> 4'
- gem.add_development_dependency 'sinatra'
- gem.add_development_dependency 'slim'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'actionmailer', '~> 3'
gem.add_development_dependency 'activerecord', '~> 3'
diff --git a/web/locales/en.yml b/web/locales/en.yml
new file mode 100644
index 00000000..dc73cf8c
--- /dev/null
+++ b/web/locales/en.yml
@@ -0,0 +1,58 @@
+en:
+ Dashboard: Dashboard
+ Status: Status
+ Time: Time
+ Namespace: Namespace
+ Realtime: Real-time
+ History: History
+ Busy: Busy
+ Processed: Processed
+ Failed: Failed
+ Scheduled: Scheduled
+ Retries: Retries
+ Enqueued: Enqueued
+ Worker: Worker
+ Workers: Workers
+ LivePoll: Live Poll
+ StopPolling: Stop Polling
+ Queue: Queue
+ Class: Class
+ Job: Job
+ Arguments: Arguments
+ Started: Started
+ ShowAll: Show All
+ CurrentMessagesInQueue: "Current messages in %{queue}"
+ Delete: Delete
+ AreYouSureDeleteJob: Are you sure you want to delete this job?
+ AreYouSureDeleteQueue: Are you sure you want to delete the %{queue} queue?
+ Queues: Queues
+ Size: Size
+ Actions: Actions
+ NextRetry: Next Retry
+ RetryCount: Retry Count
+ RetryNow: Retry Now
+ LastRetry: Last Retry
+ OriginallyFailed: Originally Failed
+ AreYouSure: Are you sure?
+ DeleteAll: Delete All
+ RetryAll: Retry All
+ NoRetriesFound: No retries were found
+ Error: Error
+ ErrorClass: Error Class
+ ErrorMessage: Error Message
+ ErrorBacktrace: Error Backtrace
+ GoBack: ← Back
+ NoScheduledFound: No scheduled jobs were found
+ When: When
+ ScheduledJobs: Scheduled Jobs
+ idle: idle
+ active: active
+ Version: Version
+ Connections: Connections
+ MemoryUsage: Memory Usage
+ PeakMemoryUsage: Peak Memory Usage
+ Uptime: Uptime (days)
+ OneWeek: 1 week
+ OneMonth: 1 month
+ ThreeMonths: 3 months
+ SixMonths: 6 months
diff --git a/web/views/_nav.slim b/web/views/_nav.slim
old mode 100755
new mode 100644
index cc8d22a2..69d3ed1e
--- a/web/views/_nav.slim
+++ b/web/views/_nav.slim
@@ -6,7 +6,7 @@
- tabs.each do |title, url|
- if url == ''
li class="#{(current_path == url) ? 'active':''}"
- a href='#{{root_path}}#{{url}}' #{title}
+ a href='#{{root_path}}#{{url}}' = t(title)
- else
li class="#{(current_path =~ Regexp.new(url)) ? 'active':''}"
- a href='#{{root_path}}#{{url}}' #{title}
+ a href='#{{root_path}}#{{url}}' = t(title)
diff --git a/web/views/_paging.slim b/web/views/_paging.slim
old mode 100755
new mode 100644
diff --git a/web/views/_status.slim b/web/views/_status.slim
old mode 100755
new mode 100644
index f7dd0dd2..4bba99ef
--- a/web/views/_status.slim
+++ b/web/views/_status.slim
@@ -1,3 +1,3 @@
span.status
i class="status-sprite status-#{current_status}"
- == current_status
+ == t(current_status)
diff --git a/web/views/_summary.slim b/web/views/_summary.slim
old mode 100755
new mode 100644
index 7cee24f6..0b885151
--- a/web/views/_summary.slim
+++ b/web/views/_summary.slim
@@ -1,19 +1,19 @@
ul.unstyled.summary
li.processed
span.count #{number_with_delimiter(stats.processed)}
- span.desc Processed
+ span.desc = t('Processed')
li.failed
span.count #{number_with_delimiter(stats.failed)}
- span.desc Failed
+ span.desc = t('Failed')
li.busy
span.count #{number_with_delimiter(workers.size)}
- span.desc Busy
+ span.desc = t('Busy')
li.scheduled
span.count #{number_with_delimiter(stats.scheduled_size)}
- span.desc Scheduled
+ span.desc = t('Scheduled')
li.retries
span.count #{number_with_delimiter(stats.retry_size)}
- span.desc Retries
+ span.desc = t('Retries')
li.enqueued
span.count #{number_with_delimiter(stats.enqueued)}
- span.desc Enqueued
+ span.desc = t('Enqueued')
diff --git a/web/views/_workers.slim b/web/views/_workers.slim
old mode 100755
new mode 100644
index 8bf8423f..d85b99e7
--- a/web/views/_workers.slim
+++ b/web/views/_workers.slim
@@ -1,10 +1,10 @@
table class="workers table table-hover table-bordered table-striped table-white"
thead
- th Worker
- th Queue
- th Class
- th Arguments
- th Started
+ th = t('Worker')
+ th = t('Queue')
+ th = t('Class')
+ th = t('Arguments')
+ th = t('Started')
- workers.each_with_index do |(worker, msg), index|
tr
td= worker
@@ -14,7 +14,7 @@ table class="workers table table-hover table-bordered table-striped table-white"
td
- if msg['payload']['args'].to_s.size > 100
= msg['payload']['args'].inspect[0..100] + "... "
- button data-toggle="collapse" data-target="#worker_#{index}" class="btn btn-mini" Show All
+ button data-toggle="collapse" data-target="#worker_#{index}" class="btn btn-mini" = t('ShowAll')
.toggle[id="worker_#{index}" style="display: none;max-width: 750px;"]= msg['payload']['args']
- else
= msg['payload']['args']
diff --git a/web/views/dashboard.slim b/web/views/dashboard.slim
index bafc6bfd..a0a4d23d 100644
--- a/web/views/dashboard.slim
+++ b/web/views/dashboard.slim
@@ -1,20 +1,20 @@
script type="text/javascript" src="#{{root_path}}javascripts/dashboard.js"
h3
- | Dashboard
+ = t('Dashboard')
span.beacon
.ring
.dot
-h5 Real-time
+h5 = t('Realtime')
#realtime
h5
- span.history-heading History
- a href="#{{root_path}}?days=7" class="history-graph #{{"active" if params[:days] == "7"}}" 1 week
- a href="#{{root_path}}" class="history-graph #{{"active" if params[:days].nil? || params[:days] == "30"}}" 1 month
- a href="#{{root_path}}?days=90" class="history-graph #{{"active" if params[:days] == "90"}}" 3 month
- a href="#{{root_path}}?days=180" class="history-graph #{{"active" if params[:days] == "180"}}" 6 month
+ span.history-heading = t('History')
+ a href="#{{root_path}}?days=7" class="history-graph #{{"active" if params[:days] == "7"}}" = t('OneWeek')
+ a href="#{{root_path}}" class="history-graph #{{"active" if params[:days].nil? || params[:days] == "30"}}" = t('OneMonth')
+ a href="#{{root_path}}?days=90" class="history-graph #{{"active" if params[:days] == "90"}}" = t('ThreeMonths')
+ a href="#{{root_path}}?days=180" class="history-graph #{{"active" if params[:days] == "180"}}" = t('SixMonths')
#history data-processed="#{Sidekiq.dump_json(@processed_history)}" data-failed="#{Sidekiq.dump_json(@failed_history)}" data-update-url="#{{root_path}}dashboard/stats"
br
@@ -23,24 +23,24 @@ h5 Redis
- if @redis_info.fetch("redis_version", nil)
.stat
h3.redis_version= @redis_info.fetch("redis_version")
- p Version
+ p = t('Version')
- if @redis_info.fetch("uptime_in_days", nil)
.stat
h3.uptime_in_days= @redis_info.fetch("uptime_in_days")
- p Uptime (days)
+ p = t('Uptime')
- if @redis_info.fetch("connected_clients", nil)
.stat
h3.connected_clients= @redis_info.fetch("connected_clients")
- p Connections
+ p = t('Connections')
- if @redis_info.fetch("used_memory_human", nil)
.stat
h3.used_memory_human= @redis_info.fetch("used_memory_human")
- p Memory Usage
+ p = t('MemoryUsage')
- if @redis_info.fetch("used_memory_peak_human", nil)
.stat
h3.used_memory_peak_human= @redis_info.fetch("used_memory_peak_human")
- p Peak Memory Usage
\ No newline at end of file
+ p = t('PeakMemoryUsage')
diff --git a/web/views/index.slim b/web/views/index.slim
old mode 100755
new mode 100644
index af780472..3078673b
--- a/web/views/index.slim
+++ b/web/views/index.slim
@@ -1,10 +1,10 @@
.row.header
.span7
- h3 Workers
+ h3 = t('Workers')
== slim :_workers
- if workers.size > 0
.row
.span2.pull-right
form action="#{root_path}reset" method="post"
- button.btn.btn-primary.btn-block type="submit" title="If you kill -9 Sidekiq, this table can fill up with old data." Clear Worker List
+ button.btn.btn-primary.btn-block type="submit" title="#{t('Kill9Warning')}" = t('ClearWorkerList')
diff --git a/web/views/layout.slim b/web/views/layout.slim
old mode 100755
new mode 100644
index 924e6446..7ae909db
--- a/web/views/layout.slim
+++ b/web/views/layout.slim
@@ -23,26 +23,26 @@ html
li
p.navbar-text Redis: #{location}
li
- p.navbar-text Time: #{Time.now.utc.strftime('%H:%M:%S UTC')}
+ p.navbar-text #{t('Time')}: #{Time.now.utc.strftime('%H:%M:%S UTC')}
- if namespace
li
- p.navbar-text Namespace: #{namespace}
+ p.navbar-text #{t('Namespace')}: #{namespace}
#page
.container
.row
.span2.summary_bar
h4
- span.title Status
+ span.title = t('Status')
== slim :_status
== slim :_summary
- unless current_path == ''
.row
.span2
- if params[:poll]
- a#live-poll.btn.btn-block.btn-primary.active href='#{{root_path}}#{{current_path}}' Stop Polling
+ a#live-poll.btn.btn-block.btn-primary.active href='#{{root_path}}#{{current_path}}' = t('StopPolling')
- else
- a#live-poll.btn.btn-block.btn-primary href='#{{root_path}}#{{current_path}}?poll=true' Live Poll
+ a#live-poll.btn.btn-block.btn-primary href='#{{root_path}}#{{current_path}}?poll=true' = t('LivePoll')
.span10
== yield
diff --git a/web/views/queue.slim b/web/views/queue.slim
old mode 100755
new mode 100644
index af1b96f5..715c21ec
--- a/web/views/queue.slim
+++ b/web/views/queue.slim
@@ -1,15 +1,14 @@
header.row
.span5
h3
- | Current messages in
- span.title #{@name}
+ == t('CurrentMessagesInQueue', :queue => @name)
.span4
== slim :_paging, :locals => { :url => "#{root_path}queues/#{@name}" }
table class="queue table table-hover table-bordered table-striped"
thead
- th Class
- th Arguments
+ th = t('Class')
+ th = t('Arguments')
th
- @messages.each_with_index do |msg, index|
tr
@@ -17,12 +16,12 @@ table class="queue table table-hover table-bordered table-striped"
td
- if msg['args'] and msg['args'].to_s.size > 100
= msg['args'].inspect[0..100] + "... "
- button data-toggle="collapse" data-target="#worker_#{index}" class="btn btn-mini" Show All
+ button data-toggle="collapse" data-target="#worker_#{index}" class="btn btn-mini" = t('ShowAll')
.toggle[id="worker_#{index}" style="display: none;"]= msg['args']
- else
= msg['args']
td
form action="#{root_path}queues/#{@name}/delete" method="post"
input name="key_val" value="#{Sidekiq.dump_json(msg)}" type="hidden"
- input.btn.btn-danger.btn-mini type="submit" name="delete" value="Delete" data-confirm="Are you sure you want to delete this job?"
+ input.btn.btn-danger.btn-mini type="submit" name="delete" value="#{t('Delete')}" data-confirm="#{t('AreYouSure')}"
== slim :_paging, :locals => { :url => "#{root_path}queues/#{@name}" }
diff --git a/web/views/queues.slim b/web/views/queues.slim
old mode 100755
new mode 100644
index f9f4f8ef..1b930213
--- a/web/views/queues.slim
+++ b/web/views/queues.slim
@@ -1,10 +1,10 @@
-h3 Queues
+h3 = t('Queues')
table class="queues table table-hover table-bordered table-striped table-white"
thead
- th Queue
- th Size
- th Actions
+ th = t('Queue')
+ th = t('Size')
+ th = t('Actions')
- @queues.each do |queue, size|
tr
td
@@ -12,4 +12,4 @@ table class="queues table table-hover table-bordered table-striped table-white"
td= number_with_delimiter(size)
td width="20%"
form action="#{root_path}queues/#{queue}" method="post"
- input.btn.btn-danger.btn-small type="submit" name="delete" value="Delete" data-confirm="Are you sure you want to delete the #{queue} queue?"
\ No newline at end of file
+ input.btn.btn-danger.btn-small type="submit" name="delete" value="#{t('Delete')}" data-confirm="#{t('AreYouSureDeleteQueue', :queue => queue)}"
diff --git a/web/views/retries.slim b/web/views/retries.slim
old mode 100755
new mode 100644
index 2c295b9a..44649985
--- a/web/views/retries.slim
+++ b/web/views/retries.slim
@@ -1,6 +1,6 @@
header.row
.span5
- h3 Retries
+ h3 = t('Retries')
.span4
- if @retries.size > 0
== slim :_paging, :locals => { :url => "#{root_path}retries" }
@@ -12,11 +12,11 @@ header.row
tr
th width="20px"
input type="checkbox" class="check_all"
- th width="25%" Next Retry
- th width="11%" Retry count
- th Queue
- th Worker
- th Args
+ th width="25%" = t('NextRetry')
+ th width="11%" = t('RetryCount')
+ th = t('Queue')
+ th = t('Worker')
+ th = t('Arguments')
- @retries.each do |msg, score|
tr
td
@@ -28,13 +28,13 @@ header.row
a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']}
td= msg['class']
td= display_args(msg['args'])
- input.btn.btn-primary.btn-small.pull-left type="submit" name="retry" value="Retry Now"
- input.btn.btn-danger.btn-small.pull-left type="submit" name="delete" value="Delete"
+ input.btn.btn-primary.btn-small.pull-left type="submit" name="retry" value="#{t('RetryNow')}"
+ input.btn.btn-danger.btn-small.pull-left type="submit" name="delete" value="#{t('Delete')}"
form action="#{root_path}retries/all/delete" method="post"
- input.btn.btn-danger.btn-small.pull-right type="submit" name="delete" value="Delete All" data-confirm="Are you sure?"
+ input.btn.btn-danger.btn-small.pull-right type="submit" name="delete" value="#{t('DeleteAll')}" data-confirm="#{t('AreYouSure')}"
form action="#{root_path}retries/all/retry" method="post"
- input.btn.btn-danger.btn-small.pull-right type="submit" name="retry" value="Retry All" data-confirm="Are you sure?"
+ input.btn.btn-danger.btn-small.pull-right type="submit" name="retry" value="#{t('RetryAll')}" data-confirm="#{t('AreYouSure')}"
- else
- .alert.alert-success No retries were found
+ .alert.alert-success = t('NoRetriesFound')
diff --git a/web/views/retry.slim b/web/views/retry.slim
old mode 100755
new mode 100644
index a8842d43..e334b8b8
--- a/web/views/retry.slim
+++ b/web/views/retry.slim
@@ -1,55 +1,55 @@
header
- h3 Job
+ h3 = t('Job')
table class="retry table table-bordered table-striped"
tbody
tr
- th Queue
+ th = t('Queue')
td
a href="#{root_path}queues/#{@retry['queue']}" #{@retry['queue']}
tr
- th Job Class
+ th = t('Class')
td
code= @retry['class']
tr
- th Job Arguments
+ th = t('Arguments')
td
code= display_args(@retry['args'], 1000)
tr
- th Job ID
+ th JID
td
code= @retry.jid
- if @retry['retry_count'] > 0
tr
- th Retry Count
+ th = t('RetryCount')
td= @retry['retry_count']
tr
- th Last Retry
+ th = t('LastRetry')
td== relative_time(@retry['retried_at'].is_a?(Numeric) ? Time.at(@retry['retried_at']) : Time.parse(@retry['retried_at']))
- else
tr
- th Originally Failed
+ th = t('OriginallyFailed')
td== relative_time(@retry['failed_at'].is_a?(Numeric) ? Time.at(@retry['failed_at']) : Time.parse(@retry['failed_at']))
tr
- th Next Retry
+ th = t('NextRetry')
td== relative_time(Time.at(@retry.score))
-h3 Error
+h3 = t('Error')
table class="error table table-bordered table-striped"
tbody
tr
- th Error Class
+ th = t('ErrorClass')
td
code= @retry['error_class']
tr
- th Error Message
+ th = t('ErrorMessage')
td= @retry['error_message']
- if !@retry['error_backtrace'].nil?
tr
- th Error Backtrace
+ th = t('ErrorBacktrace')
td
code== @retry['error_backtrace'].join("
")
form.form-horizontal action="#{root_path}retries/#{job_params(@retry, @retry.score)}" method="post"
- a.btn href="#{root_path}retries" ← Back
- input.btn.btn-primary type="submit" name="retry" value="Retry Now"
- input.btn.btn-danger type="submit" name="delete" value="Delete"
+ a.btn href="#{root_path}retries" = t('GoBack')
+ input.btn.btn-primary type="submit" name="retry" value="#{t('RetryNow')}"
+ input.btn.btn-danger type="submit" name="delete" value="#{t('Delete')}"
diff --git a/web/views/scheduled.slim b/web/views/scheduled.slim
old mode 100755
new mode 100644
index 66931acd..a390c1f4
--- a/web/views/scheduled.slim
+++ b/web/views/scheduled.slim
@@ -1,6 +1,6 @@
header.row
.span5
- h3 Scheduled Jobs
+ h3 = t('ScheduledJobs')
.span4
- if @scheduled.size > 0
== slim :_paging, :locals => { :url => "#{root_path}scheduled" }
@@ -12,10 +12,10 @@ header.row
thead
th width="20px"
input type="checkbox" class="check_all"
- th width="25%" When
- th width="10%" Queue
- th Worker
- th Args
+ th width="25%" = t('When')
+ th width="10%" = t('Queue')
+ th = t('Worker')
+ th = t('Arguments')
- @scheduled.each do |msg, score|
tr
td
@@ -25,6 +25,6 @@ header.row
a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']}
td= msg['class']
td= display_args(msg['args'])
- input.btn.btn-danger.pull-right type="submit" name="delete" value="Delete"
+ input.btn.btn-danger.pull-right type="submit" name="delete" value="#{t('Delete')}"
- else
- .alert.alert-success No scheduled jobs were found
+ .alert.alert-success = t('NoScheduledFound')