rosette 0.1.1 → 0.2.0

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.
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