i18n_admin 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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