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
@@ -0,0 +1,40 @@
1
+ module I18nAdmin
2
+ module ApplicationHelper
3
+ # Converts flash types to :success or :error to conform to what
4
+ # twitter bootstrap can handle
5
+ #
6
+ def homogenize_flash_type type
7
+ case type
8
+ when :notice then :success
9
+ when :alert then :warning
10
+ when :error then :danger
11
+ else type
12
+ end
13
+ end
14
+
15
+ def display_flash
16
+ # Get devise errors if present
17
+ if respond_to?(:devise_error_messages!)
18
+ flash[:alert] = devise_error_messages! if defined?(resource) && devise_error_messages! != ""
19
+ end
20
+
21
+ # Render empty string if no flash
22
+ return "" if flash.empty?
23
+
24
+ # Make a div.alert for each message and join the whole
25
+ messages = flash.map do |type, message|
26
+ flash.delete(type)
27
+ content_tag :div, class: "alert alert-block alert-#{ homogenize_flash_type(type) } alert-dismissable" do
28
+ buffer = content_tag(:button, type: "button", class: "close", "data-dismiss" => "alert") do
29
+ content_tag(:span, '×'.html_safe)
30
+ end
31
+
32
+ buffer += content_tag(:p, message.html_safe)
33
+ end
34
+ end
35
+
36
+ # Join the messages and make sure markup is correctly displayed
37
+ messages.join.html_safe
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ module I18nAdmin
2
+ class ExportFile < ResourceFile
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module I18nAdmin
2
+ class ImportFile < ResourceFile
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module I18nAdmin
2
+ class ImportJob < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ module I18nAdmin
2
+ class ResourceFile < ActiveRecord::Base
3
+ has_attached_file :file
4
+ do_not_validate_attachment_file_type :file
5
+
6
+ validates :job_id, presence: true
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module I18nAdmin
2
+ class TranslationsSet < ActiveRecord::Base
3
+ validates :locale, presence: true
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module I18nAdmin
2
+ class WhitelistedResource < ActiveRecord::Base
3
+ belongs_to :resource, polymorphic: true
4
+
5
+ def self.for(resource)
6
+ where(resource_id: resource.id, resource_type: resource.class.name).first_or_initialize
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ = modal id: 'job-queued-modal', data: { :'i18n-job-monitoring' => true, :'monitor-url' => job_monitor.job_progress_path(@job_id) } do |modal|
2
+ = modal.header do
3
+ = t('i18n_admin.exports.queued.title')
4
+
5
+ = modal.body do
6
+ %p= t('i18n_admin.exports.queued.please_wait')
7
+
8
+ %p.help-block= t('i18n_admin.exports.queued.help')
9
+
10
+ %hr
11
+
12
+ .progress
13
+ .progress-bar.progress-bar-info.progress-bar-striped.active{ style: 'width:100%;' }
14
+
15
+ = modal.footer do
16
+ = link_to '#', class: 'btn btn-default', data: { cancel: 'job' } do
17
+ %i.fa.fa-times
18
+ = t('i18n_admin.shared.cancel')
@@ -0,0 +1,61 @@
1
+ <div class="container-fluid main-container">
2
+ <div class="panel panel-default">
3
+ <div class="panel-heading">
4
+ <h1 class="panel-title">
5
+ <%= t('i18n_admin.exports.title') %>
6
+ </h1>
7
+ </div>
8
+
9
+ <div class="panel-body">
10
+ <div class="list-group">
11
+ <% I18n.available_locales.each do |locale| %>
12
+ <%= link_to export_path(locale: locale, format: (:xls unless I18nAdmin.async_io)), class: 'list-group-item', remote: I18nAdmin.async_io, data: { :'export-link' => true } do %>
13
+ <i class="glyphicon glyphicon-download"></i>
14
+ <%= locale %>
15
+ <% end %>
16
+ <% end %>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <hr>
22
+
23
+ <div class="panel panel-default">
24
+ <div class="panel-heading">
25
+ <h1 class="panel-title">
26
+ <%= t('i18n_admin.imports.title') %>
27
+ </h1>
28
+ </div>
29
+
30
+ <% if @import && @import.errors.any? %>
31
+ <div class="panel-heading import-errors danger">
32
+ <% @import.errors.each do |type, errors| %>
33
+ <%= render partial: "i18n_admin/imports/errors/#{ type }", locals: { errors: errors } %>
34
+ <% end %>
35
+ </div>
36
+ <% end %>
37
+
38
+ <div class="panel-body">
39
+ <%= form_tag import_path, multipart: true do %>
40
+ <div class="form-group">
41
+ <%= label_tag :file, t('i18n_admin.imports.file.label') %>
42
+ <%= file_field_tag :file %>
43
+ <p class="help-block">
44
+ <%= t('i18n_admin.imports.file.hint') %>
45
+ </p>
46
+ </div>
47
+
48
+ <div class="form-group">
49
+ <%= label_tag :locale, t('i18n_admin.imports.locale.label') %>
50
+ <%= select_tag :locale, options_for_select(I18n.available_locales), include_blank: true %>
51
+ </div>
52
+
53
+ <div class="form-actions">
54
+ <button type="submit" class="btn btn-primary">
55
+ <%= t('i18n_admin.imports.submit') %>
56
+ </button>
57
+ </div>
58
+ <% end %>
59
+ </div>
60
+ </div>
61
+ </div>
@@ -0,0 +1,33 @@
1
+ <div class="container-fluid main-container" data-processing-import data-check-url="<%= import_path(id: @job) %>">
2
+ <div class="panel panel-default">
3
+ <div class="panel-heading">
4
+ <h1 class="panel-title">
5
+ <%= t('i18n_admin.imports.processing.title') %>
6
+ </h1>
7
+ </div>
8
+
9
+ <div class="panel-body" data-status="pending">
10
+ <p class="help-block text-center">
11
+ <%= t('i18n_admin.imports.processing.help_text') %>
12
+ <br><br>
13
+ <%= image_tag 'i18n_admin/spinner.gif' %>
14
+ </p>
15
+ </div>
16
+
17
+ <div class="panel-body hidden" data-status="success">
18
+ <div class="alert alert-success">
19
+ <%= t('i18n_admin.imports.run.success') %>
20
+ </div>
21
+
22
+ <%= link_to t('i18n_admin.shared.back'), new_import_path, class: 'btn btn-primary' %>
23
+ </div>
24
+
25
+ <div class="panel-body hidden" data-status="error">
26
+ <div class="alert alert-error">
27
+ <%= t('i18n_admin.imports.run.errors') %>
28
+ </div>
29
+
30
+ <%= link_to t('i18n_admin.shared.back'), new_import_path, class: 'btn btn-primary' %>
31
+ </div>
32
+ </div>
33
+ </div>
File without changes
@@ -0,0 +1,3 @@
1
+ <div class="pagination-container">
2
+ <%= paginate(translations) %>
3
+ </div>
@@ -0,0 +1,9 @@
1
+ <tr class="translation-row" data-translation>
2
+ <td width="20%" class="translation-key-cell" title="<%= translation.key %>"><%= translation.key %></td>
3
+ <td width="40%"><%= translation.original %></td>
4
+ <td width="40%" class="input-cell">
5
+ <%= form_for(translation, url: translation_path(translation, locale: current_locale), remote: true, html: { data: { :'translation-form' => true } }) do |form| %>
6
+ <%= form.text_area :value, class: 'form-control translation-input', data: { :'translation-field' => true } %>
7
+ <% end %>
8
+ </td>
9
+ </tr>
@@ -0,0 +1,81 @@
1
+ <div class="container-fluid main-container">
2
+ <div class="panel panel-default">
3
+ <div class="panel-heading">
4
+ <div class="row">
5
+ <div class="col-md-6">
6
+ <h1 class="panel-title">
7
+ <%= t('i18n_admin.translations.title') %>
8
+ </h1>
9
+ </div>
10
+
11
+ <div class="col-md-6">
12
+ <div class="pull-right">
13
+ <h2>
14
+ <%= t('i18n_admin.translations.choose_locale') %>
15
+ </h2>
16
+
17
+ <div class="btn-group">
18
+ <% @locales.each do |locale| %>
19
+ <%= link_to translations_path(locale: locale), class: "btn btn-primary#{ ' active' if current_locale == locale }" do %>
20
+ <%= locale %>
21
+ <% end %>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="panel-heading panel-subheading">
30
+ <div class="row">
31
+ <div class="col-md-6 pagination-column">
32
+ <%= render partial: 'pagination', locals: { translations: @translations } %>
33
+ </div>
34
+
35
+ <div class="col-md-6">
36
+ <%= form_tag translations_path, method: :get do %>
37
+ <div class="input-group">
38
+ <span class="input-group-addon">
39
+ <i class="glyphicon glyphicon-search"></i>
40
+ </span>
41
+
42
+ <%= text_field_tag :q, params[:q], class: 'form-control' %>
43
+ <%= hidden_field_tag :locale, current_locale %>
44
+
45
+ <span class="input-group-btn">
46
+ <button class="btn btn-primary" type="submit">
47
+ <%= t('i18n_admin.translations.search.submit') %>
48
+ </button>
49
+
50
+ <% if params[:q].present? %>
51
+ <%= link_to translations_path(locale: current_locale), class: 'btn btn-danger' do %>
52
+ <%= t('i18n_admin.translations.search.cancel') %>
53
+ <% end %>
54
+ <% end %>
55
+ </span>
56
+ </div>
57
+ <% end %>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="panel-body">
63
+ <table class="table table-bordered">
64
+ <thead>
65
+ <tr>
66
+ <th width="20%"><%= t('i18n_admin.translation_set.key') %></th>
67
+ <th width="40%"><%= t('i18n_admin.translation_set.original', default_locale: I18n.default_locale) %></th>
68
+ <th width="40%"><%= t('i18n_admin.translation_set.translation') %></th>
69
+ </tr>
70
+ </thead>
71
+ <tbody>
72
+ <%= render @translations %>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+
77
+ <div class="panel-footer">
78
+ <%= render partial: 'pagination', locals: { translations: @translations } %>
79
+ </div>
80
+ </div>
81
+ </div>
@@ -0,0 +1,11 @@
1
+ !!!
2
+ %html{ lang: I18n.locale }
3
+ %head
4
+ %title Translations Admin
5
+ = stylesheet_link_tag "i18n_admin/application", media: "all"
6
+ = javascript_include_tag "i18n_admin/application"
7
+ = csrf_meta_tags
8
+
9
+ %body
10
+ = display_flash
11
+ = yield
@@ -0,0 +1,38 @@
1
+ en:
2
+ i18n_admin:
3
+ translations:
4
+ title: 'Translations'
5
+ choose_locale: 'Choose locale to translate :'
6
+ search:
7
+ submit: 'Search'
8
+ cancel: 'Cancel search'
9
+
10
+ translation_set:
11
+ key: 'Key'
12
+ original: "Original (%{default_locale})"
13
+ translation: 'Translation'
14
+
15
+ exports:
16
+ title: "Export translations file"
17
+
18
+ imports:
19
+ title: "Import translations file"
20
+ file:
21
+ label: "Translations file"
22
+ hint: "File in .xls format, from the exported file template"
23
+ locale:
24
+ label: "Locale to update"
25
+ submit: "Update translations"
26
+ please_choose_file: "Please choose a file to upload"
27
+ processing:
28
+ title: "Importing"
29
+ help_text: |
30
+ The translations file is being imported.
31
+ This can take from a few seconds to several minutes, depending on
32
+ the size of the imported file.
33
+ run:
34
+ success: "The file has been successfully imported"
35
+ errors: "The file was imported, but some errors happened"
36
+
37
+ shared:
38
+ back: "Back"
@@ -0,0 +1,52 @@
1
+ fr:
2
+ i18n_admin:
3
+ translations:
4
+ title: 'Traductions'
5
+ choose_locale: 'Choisissez la langue à traduire :'
6
+ search:
7
+ submit: 'Rechercher'
8
+ cancel: 'Annuler la recherche'
9
+
10
+ translation_set:
11
+ key: 'Clé'
12
+ original: 'Original (%{default_locale})'
13
+ translation: 'Traduction'
14
+
15
+ exports:
16
+ title: "Exporter les traductions"
17
+ queued:
18
+ title: "Export en cours ..."
19
+ please_wait: "Merci de patienter quelques instants."
20
+ help: |
21
+ L'export est en cours de traitement. Une fois terminé, le
22
+ téléchargement du fichier de traduction vous sera automatiquement
23
+ proposé.
24
+ file:
25
+ name: "traductions_%{lang}-%{time}.%{ext}"
26
+ columns:
27
+ key: "Clé"
28
+ original: "Original (%{lang})"
29
+ translated: "Traduction (%{lang})"
30
+
31
+ imports:
32
+ title: "Importer un fichier de traductions"
33
+ file:
34
+ label: "Fichier de traductions"
35
+ hint: "Fichier au format .xls, à partir du modèle du fichier exporté"
36
+ locale:
37
+ label: "Langue à mettre à jour"
38
+ submit: "Mettre à jour les traductions"
39
+ please_choose_file: "Merci de choisir un fichier à envoyer"
40
+ processing:
41
+ title: "Import en cours"
42
+ help_text: |
43
+ L'import des traductions est en cours.
44
+ Cette opération peut prendre de quelques secondes à plusieurs minutes,
45
+ en fonction de la taille du fichier importé.
46
+ run:
47
+ success: "Le fichier a bien été importé"
48
+ errors: "Le fichier a été importé, mais certaines erreurs ont eu lieu"
49
+
50
+ shared:
51
+ back: "Retour"
52
+ cancel: "Annuler"
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ I18nAdmin::Engine.routes.draw do
2
+ resources :translations, only: [:index, :update]
3
+
4
+ resource :export, only: [:show]
5
+ resource :import, only: [:show, :new, :create]
6
+ end
@@ -0,0 +1,12 @@
1
+ class CreateI18nAdminTranslationsSets < ActiveRecord::Migration
2
+ def change
3
+ enable_extension :hstore
4
+
5
+ create_table :i18n_admin_translations_sets do |t|
6
+ t.string :locale
7
+ t.hstore :translations
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ class CreateI18nAdminImportJobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :i18n_admin_import_jobs do |t|
4
+ t.string :locale
5
+ t.text :filename
6
+ t.string :state, default: 'pending'
7
+
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end