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 +4 -4
- data/README.md +16 -8
- data/app/assets/stylesheets/rosette/application.css +83 -15
- data/app/controllers/rosette/translations_controller.rb +34 -0
- data/app/views/layouts/rosette/application.html.erb +9 -9
- data/app/views/rosette/new.html.erb +28 -0
- data/config/routes.rb +1 -0
- data/lib/rosette/controller.rb +21 -0
- data/lib/rosette/engine.rb +12 -0
- data/lib/rosette/manager.rb +69 -0
- data/lib/rosette/version.rb +1 -1
- data/lib/rosette.rb +9 -2
- metadata +19 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dffb250e67fdc3f3935d85dbbe875fa56b8843629b138cb4444f7e5f2166cb6b
|
4
|
+
data.tar.gz: d2dcf4ab94c13916db15b7739661b86fcf06ad1f8fa16e73fffbd8f1d51793b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
3
|
+
<head>
|
4
|
+
<title>Rosette</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
7
|
|
8
|
-
|
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
@@ -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
|
data/lib/rosette/engine.rb
CHANGED
@@ -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
|
data/lib/rosette/version.rb
CHANGED
data/lib/rosette.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
|
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
|
-
|
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.
|
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
|