rosette 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '03866ba93b8b990e22574cb0d1f84a6e2059988ca1bd8e091ab8551aaa4db1d9'
4
- data.tar.gz: 26c70b8d2e6999dde21bd11fe0972505d2fc9b31b554ece1528f2f50aedd9dae
3
+ metadata.gz: dffb250e67fdc3f3935d85dbbe875fa56b8843629b138cb4444f7e5f2166cb6b
4
+ data.tar.gz: d2dcf4ab94c13916db15b7739661b86fcf06ad1f8fa16e73fffbd8f1d51793b5
5
5
  SHA512:
6
- metadata.gz: d29a828701e75efa5654cacdfc353c34066dae80befff846f326ecf24611aa27396fdd8f0da8f233fd59fc818c9cb9867d1993b35e72d0c02aca392bb0acb171
7
- data.tar.gz: f8a993fe6be29486261e7495e2992610e14af47d9e7538ed54ac95ddf992f7122c661cf617a4fa650e008767b9327dc19a4ee6ee17b3ff896ef5a1e99204cc6a
6
+ metadata.gz: f559af7434bb4ccff09fa5eb0ba4532559fe424621846ea910bf050facce940b61b3e0edf1e09f37e2862b8f2e0083482d25e46b3412d8769aeba41c90a2f2d9
7
+ data.tar.gz: 99f57b0c1edf0b156d76aec004fe200430940f4318229ca2d34d0b1b2d3cc57124bacffab894252bf7f3ff58a5eb5edd8b452067fad0d7f2b0dfa4101196bda4
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Rosette
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ Rosette is a Ruby on Rails engine that helps you add missing translations to your application. If your main app is configured to raise on missing translations, Rosette will catch any `I18n::MissingTranslationData` error and display a form to add the missing translations. The form includes an input for each available locale set by `config.i18n.available_locales`.
6
4
 
7
5
  ## Installation
6
+
8
7
  Add this line to your application's Gemfile:
9
8
 
10
9
  ```ruby
@@ -16,13 +15,22 @@ And then execute:
16
15
  $ bundle
17
16
  ```
18
17
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install rosette
18
+ Make sure your app raises error for missing translations in development:
19
+
20
+ ```ruby
21
+ # config/environments/development.rb
22
+
23
+ config.i18n.raise_on_missing_translations = true
22
24
  ```
23
25
 
24
- ## Contributing
25
- Contribution directions go here.
26
+ Whether you currently use or are interested in starting to use [i18n-task to normalize](https://github.com/glebm/i18n-tasks#normalize-data) your locales files, add this initializer:
27
+
28
+ ```ruby
29
+ # config/initializers/rosette.rb
30
+
31
+ Rosette.normalize = true
32
+ ```
26
33
 
27
34
  ## License
35
+
28
36
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,15 +1,83 @@
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 any plugin's vendor/assets/stylesheets directory 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 other CSS/SCSS
10
- * files in this directory. Styles in this file should be added after the last require_* statement.
11
- * It is generally better to create a new file per style scope.
12
- *
13
- *= require_tree .
14
- *= require_self
15
- */
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500');
2
+
3
+ .text-center {
4
+ text-align: center;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Inter', sans-serif;
9
+ background-color: white;
10
+ margin: 5rem;
11
+ }
12
+
13
+ .form-wrapper {
14
+ background-color: rgb(241, 237, 237);
15
+ padding: 2.5rem;
16
+ margin: 32px auto;
17
+ border-radius: 0.5rem;
18
+ max-width: 38rem;
19
+ box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
20
+ position: relative;
21
+ }
22
+
23
+ .form {
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ .input-text {
29
+ margin-bottom: 1rem;
30
+ padding: 1rem;
31
+ border-radius: .375rem;
32
+ border-color: rgb(211, 211, 211);
33
+ border: solid 1px rgba(0, 0, 0, 0.16);
34
+ box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
35
+ background-color: rgb(251, 249, 249);
36
+ }
37
+
38
+ .input-text:focus {
39
+ outline: 1rem solid transparent;
40
+ outline-offset: 1rem;
41
+ border-color: rgb(163, 163, 163);
42
+ }
43
+
44
+ label {
45
+ margin-bottom: 1rem;
46
+ }
47
+
48
+ .button {
49
+ background-color: rgb(220, 38, 38);
50
+ color: white;
51
+ padding: 0.75rem 5rem;
52
+ border-color: transparent;
53
+ border-radius: .375rem;
54
+ margin: 1rem auto 0 auto;
55
+ font-size: 1rem;
56
+ line-height: 1.25rem;
57
+ cursor: pointer;
58
+ transition: background-color 0.8s;
59
+ }
60
+
61
+ .button:hover {
62
+ background-color: rgb(235, 63, 54);
63
+ }
64
+
65
+ .note {
66
+ font-size: .75rem;
67
+ color: rgb(107, 114, 128);
68
+ margin-top: 1rem;
69
+ }
70
+
71
+ .error {
72
+ color: #856404;
73
+ background-color: #fff3cd;
74
+ border-color: #ffeeba;
75
+ padding: 0.75rem 1.25rem;
76
+ margin-bottom: 1rem;
77
+ border-radius: 0.25rem;
78
+ }
79
+
80
+ .locale {
81
+ color: rgb(220, 38, 38);
82
+ font-weight: 500;
83
+ }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rosette
4
+ class TranslationsController < ApplicationController
5
+ def create
6
+ available_locales.each { |locale, translation| Manager.create(locale, key, translation) }
7
+
8
+ Manager.normalize!
9
+
10
+ redirect_to redirect_path, notice: 'Translation(s) added'
11
+ rescue StandardError => e
12
+ @error_message = e.message
13
+ render_rosette_new(key, redirect_path)
14
+ end
15
+
16
+ private
17
+
18
+ def translations_params
19
+ params.require(:translations).permit(:key, :redirect_path, available_locales: {})
20
+ end
21
+
22
+ def available_locales
23
+ translations_params[:available_locales]
24
+ end
25
+
26
+ def key
27
+ translations_params[:key]
28
+ end
29
+
30
+ def redirect_path
31
+ URI.parse(translations_params[:redirect_path]).to_s
32
+ end
33
+ end
34
+ end
@@ -1,15 +1,15 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>Rosette</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
3
+ <head>
4
+ <title>Rosette</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
7
 
8
- <%= stylesheet_link_tag "rosette/application", media: "all" %>
9
- </head>
10
- <body>
8
+ <%= stylesheet_link_tag "rosette/application", media: "all" %>
9
+ </head>
10
+ <body>
11
11
 
12
- <%= yield %>
12
+ <%= yield %>
13
13
 
14
- </body>
14
+ </body>
15
15
  </html>
@@ -0,0 +1,28 @@
1
+ <h1 class="text-center">Add missing translation</h1>
2
+
3
+ <p class="text-center locale"><%= key %></p>
4
+
5
+ <div class="form-wrapper">
6
+ <%= form_with scope: :translations, url: rosette.translations_path, class: 'form' do |f| %>
7
+ <% if @error_message %>
8
+ <p class="error"><%= @error_message %></p>
9
+ <% end %>
10
+
11
+ <%= f.hidden_field :key, value: key %>
12
+ <%= f.hidden_field :redirect_path, value: redirect_path %>
13
+
14
+ <%= f.fields_for :available_locales do |a| %>
15
+ <% Rails.configuration.i18n.available_locales.each do |locale| %>
16
+ <%= a.label locale, "#{locale.capitalize} translation:" %>
17
+ <%= a.text_field locale, required: false, label: false, class: 'input-text' %>
18
+ <% end %>
19
+ <% end %>
20
+
21
+ <%= f.button 'Submit', class: 'button' %>
22
+ <% end %>
23
+
24
+ </div>
25
+
26
+ <div class="note text-center">
27
+ If you prefer to add the translation directly to the yaml file, you can choose not to fill in the input field.
28
+ </div>
data/config/routes.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  Rosette::Engine.routes.draw do
2
+ resources :translations, only: [:create]
2
3
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rosette
4
+ module Controller
5
+ extend ActiveSupport::Concern
6
+
7
+ included { rescue_from I18n::MissingTranslationData, with: :add_missing_translation_data }
8
+
9
+ private
10
+
11
+ def add_missing_translation_data(exception)
12
+ key = exception.message.sub(/translation missing:.+?\./, '')
13
+
14
+ render_rosette_new(key, request.fullpath)
15
+ end
16
+
17
+ def render_rosette_new(key, redirect_path)
18
+ render 'rosette/new', layout: 'rosette/application', locals: { key: key, redirect_path: redirect_path }
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rosette
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace Rosette
6
+
7
+ config.after_initialize do |app|
8
+ app.routes.prepend do
9
+ mount Rosette::Engine, at: '/rosette'
10
+ end
11
+ end
12
+
13
+ ActiveSupport.on_load(:action_controller_base) do
14
+ include Rosette::Controller
15
+ end
4
16
  end
5
17
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Manager
4
+ %w[create read delete].each do |verb|
5
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
6
+ def self.#{verb}(*args)
7
+ new(*args).send('#{verb}')
8
+ end
9
+ METHOD
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(locale, key, translation = nil)
15
+ @file_path = Rails.root.join("config/locales/#{locale}.yml")
16
+ @keys = normalize "#{locale}.#{key}"
17
+ @translation = translation
18
+ end
19
+
20
+ def create
21
+ new_translation = @keys.reverse.inject(@translation) { |v, k| { k => v } }
22
+ updated_translations = deep_merge(stored_translations, new_translation)
23
+ persist(updated_translations)
24
+ end
25
+
26
+ def read
27
+ stored_translations.dig(*@keys)
28
+ end
29
+
30
+ def delete
31
+ updated_translations = delete_translation(stored_translations, @keys)
32
+ persist(updated_translations)
33
+ end
34
+
35
+ def delete_translation(translations, keys)
36
+ return translations.delete(keys.first) if keys.length == 1
37
+
38
+ head, *tail = keys
39
+ delete_translation(translations[head], tail)
40
+
41
+ translations
42
+ end
43
+
44
+ def persist(translations)
45
+ File.write(@file_path, translations.deep_stringify_keys.to_yaml)
46
+ end
47
+
48
+ def stored_translations
49
+ YAML.safe_load(File.read(@file_path), symbolize_names: true)
50
+ end
51
+
52
+ def deep_merge(hash, other_hash)
53
+ hash.merge(other_hash) do |_key, this_val, other_val|
54
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
55
+ deep_merge(this_val, other_val)
56
+ else
57
+ other_val
58
+ end
59
+ end
60
+ end
61
+
62
+ def normalize(key)
63
+ key.split('.').map(&:to_sym)
64
+ end
65
+
66
+ def self.normalize!
67
+ I18n::Tasks::CLI.start(['normalize']) if Rosette.normalize
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module Rosette
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/rosette.rb CHANGED
@@ -1,6 +1,13 @@
1
- require "rosette/version"
1
+ # frozen_string_literal: true
2
+
3
+ require "rosette/controller"
2
4
  require "rosette/engine"
5
+ require "rosette/manager"
6
+ require "rosette/version"
7
+
8
+ require "i18n/tasks/cli"
9
+ require "yaml"
3
10
 
4
11
  module Rosette
5
- # Your code goes here...
12
+ mattr_accessor :normalize
6
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rosette
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Platteeuw
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2023-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: i18n-tasks
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.12
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.12
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rails
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -51,12 +65,16 @@ files:
51
65
  - app/assets/config/rosette_manifest.js
52
66
  - app/assets/stylesheets/rosette/application.css
53
67
  - app/controllers/rosette/application_controller.rb
68
+ - app/controllers/rosette/translations_controller.rb
54
69
  - app/helpers/rosette/application_helper.rb
55
70
  - app/models/rosette/application_record.rb
56
71
  - app/views/layouts/rosette/application.html.erb
72
+ - app/views/rosette/new.html.erb
57
73
  - config/routes.rb
58
74
  - lib/rosette.rb
75
+ - lib/rosette/controller.rb
59
76
  - lib/rosette/engine.rb
77
+ - lib/rosette/manager.rb
60
78
  - lib/rosette/version.rb
61
79
  - lib/tasks/rosette_tasks.rake
62
80
  homepage: https://rubygems.org/gems/rosette