i18n_admin 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +78 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/images/i18n_admin/spinner.gif +0 -0
  6. data/app/assets/javascripts/i18n_admin/application.js +21 -0
  7. data/app/assets/javascripts/i18n_admin/src/i18n-admin-job-monitor.coffee +37 -0
  8. data/app/assets/javascripts/i18n_admin/src/import.coffee +23 -0
  9. data/app/assets/javascripts/i18n_admin/src/translation.coffee +45 -0
  10. data/app/assets/stylesheets/i18n_admin/application.css.scss +88 -0
  11. data/app/controllers/i18n_admin/application_controller.rb +21 -0
  12. data/app/controllers/i18n_admin/exports_controller.rb +19 -0
  13. data/app/controllers/i18n_admin/imports_controller.rb +28 -0
  14. data/app/controllers/i18n_admin/translations_controller.rb +30 -0
  15. data/app/helpers/i18n_admin/application_helper.rb +40 -0
  16. data/app/models/i18n_admin/export_file.rb +4 -0
  17. data/app/models/i18n_admin/import_file.rb +4 -0
  18. data/app/models/i18n_admin/import_job.rb +4 -0
  19. data/app/models/i18n_admin/resource_file.rb +8 -0
  20. data/app/models/i18n_admin/translations_set.rb +5 -0
  21. data/app/models/i18n_admin/whitelisted_resource.rb +9 -0
  22. data/app/views/i18n_admin/exports/queued.html.haml +18 -0
  23. data/app/views/i18n_admin/imports/errors/_resource_invalid.html.erb +0 -0
  24. data/app/views/i18n_admin/imports/errors/_resource_not_found.html.erb +0 -0
  25. data/app/views/i18n_admin/imports/new.html.erb +61 -0
  26. data/app/views/i18n_admin/imports/processing.html.erb +33 -0
  27. data/app/views/i18n_admin/imports/queued.html.haml +0 -0
  28. data/app/views/i18n_admin/translations/_pagination.html.erb +3 -0
  29. data/app/views/i18n_admin/translations/_translation.html.erb +9 -0
  30. data/app/views/i18n_admin/translations/index.html.erb +81 -0
  31. data/app/views/layouts/i18n_admin/application.html.haml +11 -0
  32. data/config/locales/i18n_admin.en.yml +38 -0
  33. data/config/locales/i18n_admin.fr.yml +52 -0
  34. data/config/routes.rb +6 -0
  35. data/db/migrate/20140829094415_create_i18n_admin_translations_sets.rb +12 -0
  36. data/db/migrate/20150825133437_create_i18n_admin_import_jobs.rb +11 -0
  37. data/db/migrate/20150828074645_create_i18n_admin_whitelisted_resources.rb +12 -0
  38. data/db/migrate/20160404161658_create_i18n_admin_resource_files.rb +11 -0
  39. data/lib/ext/paperclip.rb +5 -0
  40. data/lib/generators/i18n_admin/install/install_generator.rb +31 -0
  41. data/lib/generators/i18n_admin/install/templates/initializer.rb +28 -0
  42. data/lib/i18n_admin.rb +51 -0
  43. data/lib/i18n_admin/engine.rb +28 -0
  44. data/lib/i18n_admin/errors.rb +10 -0
  45. data/lib/i18n_admin/errors/base.rb +23 -0
  46. data/lib/i18n_admin/errors/collection.rb +24 -0
  47. data/lib/i18n_admin/errors/resource_invalid.rb +7 -0
  48. data/lib/i18n_admin/errors/resource_not_found.rb +7 -0
  49. data/lib/i18n_admin/export.rb +14 -0
  50. data/lib/i18n_admin/export/base.rb +129 -0
  51. data/lib/i18n_admin/export/xls.rb +110 -0
  52. data/lib/i18n_admin/hstore_backend.rb +68 -0
  53. data/lib/i18n_admin/import.rb +16 -0
  54. data/lib/i18n_admin/import/base.rb +64 -0
  55. data/lib/i18n_admin/import/job.rb +17 -0
  56. data/lib/i18n_admin/import/xls.rb +58 -0
  57. data/lib/i18n_admin/request_store.rb +11 -0
  58. data/lib/i18n_admin/translation.rb +27 -0
  59. data/lib/i18n_admin/translation_collection.rb +34 -0
  60. data/lib/i18n_admin/translations.rb +96 -0
  61. data/lib/i18n_admin/version.rb +3 -0
  62. data/lib/tasks/i18n_admin_tasks.rake +35 -0
  63. metadata +307 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 02ae7e131829c4fa8de58cc8442a66c4469849ef
4
+ data.tar.gz: 00483d502876735d4062205d0c671255cbb2216c
5
+ SHA512:
6
+ metadata.gz: f6f60e39ca4eb851df17d9a38fcac50df910c9c1b071739a29a65ae067abc1b026ceeb123124c7157d82642433ddc1ca9fa9843f405f9a604aec9b7a99d7c056
7
+ data.tar.gz: 52c1b20386afbcd82178981210e088ea07a4b08476f935f063a9c082fdc929915c5bbc05847be3778249fca79164a9d2458715403ef842d12a54920747e7bbd6
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # I18nAdmin
2
+
3
+ Admin panel to translate Rails apps with `I18n::Backend::KeyValue` stores.
4
+
5
+ **Warning : Only PostgreSQL is supported due to HSTORE field type usage to store translations**
6
+
7
+ It works by creating a I18n backend chain and plugging a custom backend before
8
+ the original YAML backend.
9
+
10
+ When you translate a key, it is stored in a `HSTORE` field, with one database
11
+ row per locale. This allows for fast translations fetching on request start.
12
+
13
+ The translations are then merged with the ones existing in the YAML, and cached
14
+ for the request duration using the
15
+ [RequestStore](https://github.com/steveklabnik/request_store) gem.
16
+
17
+ ## Install
18
+
19
+ Add to your Gemfile and bundle :
20
+
21
+ ```ruby
22
+ gem 'i18n_admin', github: 'glyph-fr/i18n_admin'
23
+ ```
24
+
25
+ Run the install generator which will mount the engine and create an initializer
26
+ template to configure the gem, and create necessary migration :
27
+
28
+ ```bash
29
+ rails generate i18n_admin:install
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ I18nAdmin gives you an admin panel to translate your static contents.
35
+ It's accessible at the Engine mount point that you choosed when running
36
+ the install generator.
37
+
38
+ The default URL is `/i18n-admin`
39
+
40
+ ### Authentication
41
+
42
+ To plug the engine with your authentication system, you can pass a method
43
+ name to execute in a `before_action` filter in the I18nAdmin controllers.
44
+
45
+ This can be done in the initializer file generated at
46
+ `config/initializers/i18n_admin.rb` :
47
+
48
+ ```ruby
49
+ I18nAdmin.config do |config|
50
+ config.authentication_method = :authenticate_user!
51
+ end
52
+ ```
53
+
54
+ ### Excluding keys from the admin
55
+
56
+ To avoid some keys to be displayed in the translation admin, you can use a
57
+ Regex in the initializer file. The regex is matched against the whole key path
58
+ (e.g. "time.formats.short")
59
+
60
+ ```ruby
61
+ I18nAdmin.config do |config|
62
+ # Exclude all date and time formats from translations
63
+ config.excluded_keys_pattern = /^(date|time)/
64
+ end
65
+ ```
66
+
67
+ ## UI locale
68
+
69
+ The locale is defined from the rails' `I18n.locale`, which is automatically
70
+ added in the `<html>` tag `lang` attribute.
71
+
72
+ The JS app then taps into this locale to set its default locale and allow
73
+ displaying side-by-side the content with the default locale and the current
74
+ locale's translation for the key
75
+
76
+ ## Licence
77
+
78
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'I18nAdmin'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,21 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require jquery.autoGrow
16
+ //= require turbolinks
17
+ //= require bootstrap
18
+ //= require nprogress
19
+ //= require nprogress-turbolinks
20
+ //= require sidekiq-job_monitor
21
+ //= require_tree ./src
@@ -0,0 +1,37 @@
1
+ class I18nAdminJobMonitor
2
+ constructor: (@$el) ->
3
+ @initializeSidekiqJobMonitor()
4
+
5
+ initializeSidekiqJobMonitor: ->
6
+ @$el.sidekiqJobMonitor(onStart: @onMonitoringStart)
7
+
8
+ onMonitoringStart: (monitor) =>
9
+ @monitor = monitor
10
+ @$modal = monitor.$el
11
+ @$modal.appendTo('body').modal()
12
+ @$modal.on('complete', @onMonitoringComplete)
13
+ @$modal.on('hide.bs.modal', @onModalHide)
14
+ @$modal.on('failed', @hideModal)
15
+ @$modal.on('click', '[data-cancel="job"]', @cancelClicked)
16
+
17
+ onMonitoringComplete: (e, monitor, data) =>
18
+ @$modal.modal('hide')
19
+ window.location.href = data.url
20
+
21
+ onModalHide: (e) =>
22
+ @cancel()
23
+
24
+ cancelClicked: =>
25
+ @hideModal()
26
+
27
+ hideModal: =>
28
+ @$modal?.modal('hide')
29
+
30
+ cancel: ->
31
+ @monitor.stopMonitoring()
32
+ @monitor.cancelJob()
33
+
34
+
35
+ $(document).on 'page:change', ->
36
+ $('[data-export-link][data-remote]').each (i, el) ->
37
+ new I18nAdminJobMonitor($(el))
@@ -0,0 +1,23 @@
1
+ class ImportProgress
2
+ constructor: (@$el) ->
3
+ @checkURL = @$el.data('check-url')
4
+ @checkProgress()
5
+
6
+ checkProgress: ->
7
+ $.getJSON(@checkURL).then (job) =>
8
+ switch job.state
9
+ when 'pending' then setTimeout($.proxy(@checkProgress, this), 1000)
10
+ when 'success' then @handleSuccess(job)
11
+ when 'error' then @handleError(job)
12
+
13
+ handleSuccess: (job) ->
14
+ $('[data-status="pending"]').addClass('hidden')
15
+ $('[data-status="success"]').removeClass('hidden')
16
+
17
+ handleError: (job) ->
18
+ $('[data-status="pending"]').addClass('hidden')
19
+ $('[data-status="error"]').removeClass('hidden')
20
+
21
+ $(document).on 'page:change', ->
22
+ if ($importProgress = $('[data-processing-import]')).length
23
+ new ImportProgress($importProgress)
@@ -0,0 +1,45 @@
1
+ class Translation
2
+ constructor: (@$el) ->
3
+ @initialize()
4
+
5
+ initialize: ->
6
+ @$form = @$el.find('[data-translation-form]')
7
+ @$field = @$el.find('[data-translation-field]')
8
+ @currentValue = @$field.val()
9
+
10
+ @$field.on('blur', $.proxy(@updateTranslation, this))
11
+ @$form.on('ajax:beforeSend', $.proxy(@onBeforeSend, this))
12
+ @$form.on('ajax:complete', $.proxy(@onSaveComplete, this))
13
+ @$form.on('ajax:success', $.proxy(@translationSaved, this))
14
+
15
+ @$field.autoGrow()
16
+
17
+ updateTranslation: ->
18
+ if (newValue = @$field.val()) != @currentValue
19
+ @currentValue = newValue
20
+ @$form.submit()
21
+
22
+ translationSaved: (e, response) ->
23
+ @onSaveComplete()
24
+
25
+ $newMarkup = $(response)
26
+ @$el.html($newMarkup.html())
27
+
28
+ @initialize()
29
+ @showSuccess()
30
+
31
+ onBeforeSend: ->
32
+ NProgress.start()
33
+ @$el.addClass('loading')
34
+
35
+ onSaveComplete: ->
36
+ NProgress.done()
37
+ @$el.removeClass('loading')
38
+
39
+ showSuccess: ->
40
+ @$field.addClass('success')
41
+ setTimeout (=> @$field.removeClass('success')), 1000
42
+
43
+ $(document).on 'page:change', ->
44
+ if ($translations = $('[data-translation]')).length
45
+ $translations.each (i, el) -> new Translation($(el))
@@ -0,0 +1,88 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require nprogress
15
+ *= require nprogress-bootstrap
16
+ *= require_self
17
+ */
18
+
19
+ @import "bootstrap";
20
+
21
+ body {
22
+ overflow: scroll;
23
+ }
24
+
25
+ .pagination {
26
+ margin-top: 0;
27
+ }
28
+
29
+ .panel-title {
30
+ font-size: 2em;
31
+ }
32
+
33
+ .panel-heading h2 {
34
+ font-size: 1em;
35
+ margin-top: 0;
36
+ }
37
+
38
+ .panel-default > .panel-heading.panel-subheading,
39
+ .panel-default > .panel-footer {
40
+ background: #fcfcfc;
41
+ }
42
+
43
+ .panel-subheading .pagination-container .text-center {
44
+ text-align: left;
45
+ }
46
+
47
+ .pagination-container .pagination {
48
+ margin-bottom: 0px;
49
+ }
50
+
51
+ .pagination-column {
52
+ border-right: 1px solid #ccc;
53
+ }
54
+
55
+ .table > tbody > tr > td.input-cell {
56
+ padding: 0;
57
+ }
58
+
59
+ .translation-input {
60
+ min-width: 400px;
61
+
62
+ &.success {
63
+ background: #DFF0D8;
64
+ }
65
+ }
66
+
67
+ td.input-cell textarea {
68
+ border-radius: 0;
69
+ border: 0;
70
+ box-shadow: 1px 2px 5px #f0f0f0 inset;
71
+ }
72
+
73
+ td.input-cell textarea:focus {
74
+ box-shadow: 1px 2px 5px #ccc inset;
75
+ }
76
+
77
+ .translation-row {
78
+ &.loading {
79
+ opacity: 0.5;
80
+ }
81
+ }
82
+
83
+ .translation-key-cell {
84
+ max-width: 100px;
85
+ text-overflow: ellipsis;
86
+ overflow: hidden;
87
+ }
88
+
@@ -0,0 +1,21 @@
1
+ module I18nAdmin
2
+ class ApplicationController < I18nAdmin.root_controller_parent.constantize
3
+ include FontAwesome::Rails::IconHelper
4
+
5
+ helper_method :current_locale
6
+
7
+ if (authentication_method = I18nAdmin.authentication_method)
8
+ before_filter authentication_method
9
+ end
10
+
11
+ def current_user
12
+ if (current_user_method = I18nAdmin.current_user_method)
13
+ send(current_user_method)
14
+ end
15
+ end
16
+
17
+ def current_locale
18
+ @current_locale ||= (params[:locale] || I18n.locale).to_sym
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module I18nAdmin
2
+ class ExportsController < I18nAdmin::ApplicationController
3
+ def show
4
+ respond_to do |format|
5
+ format.html do
6
+ @job_id = Export::XLS.perform_async(current_locale.to_s)
7
+ render 'queued', layout: false
8
+ end
9
+
10
+ format.xls do
11
+ job = Export::XLS.new
12
+ job.perform(current_locale.to_s)
13
+
14
+ redirect_to job.export_file.file.url
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module I18nAdmin
2
+ class ImportsController < I18nAdmin::ApplicationController
3
+ def show
4
+ respond_to do |format|
5
+ format.json do
6
+ job = ImportJob.find(params[:id])
7
+ render json: job.as_json(only: [:id, :filename, :state])
8
+ end
9
+ end
10
+ end
11
+
12
+ def new
13
+ end
14
+
15
+ def create
16
+ unless params[:file].present?
17
+ flash[:error] = t('i18n_admin.imports.please_choose_file')
18
+ render 'new'
19
+ return
20
+ end
21
+
22
+ @job = ImportJob.create!(locale: current_locale, filename: params[:file].original_filename)
23
+ Import::Job.perform_async(current_locale, params[:file], @job.id)
24
+
25
+ render 'processing'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module I18nAdmin
2
+ class TranslationsController < I18nAdmin::ApplicationController
3
+ before_filter :fetch_translations
4
+
5
+ def index
6
+ @translations = @translations.search(params[:q]) if params[:q]
7
+ @translations = @translations.page(params[:page]).per(60)
8
+
9
+ @locales = I18n.available_locales
10
+ end
11
+
12
+ def update
13
+ translation = @translations.find(params[:id])
14
+ translation.value = translation_params[:value]
15
+
16
+ I18nAdmin::Translations.update(translation)
17
+ render partial: 'translation', locals: { translation: translation }
18
+ end
19
+
20
+ private
21
+
22
+ def translation_params
23
+ params.require(:translation).permit(:value)
24
+ end
25
+
26
+ def fetch_translations
27
+ @translations = I18nAdmin::Translations.translations_for(current_locale)
28
+ end
29
+ end
30
+ end