rosette 0.1.1 → 0.3.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 +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/bin/rails +19 -0
- data/bin/rosette +22 -0
- data/config/routes.rb +1 -0
- data/lib/rosette/cli.rb +71 -0
- data/lib/rosette/controller.rb +21 -0
- data/lib/rosette/engine.rb +12 -0
- data/lib/rosette/manager.rb +73 -0
- data/lib/rosette/version.rb +1 -1
- data/lib/rosette.rb +14 -2
- metadata +25 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97ee90651ff8eb4ca983a10bf740934c878df05922013c149306b42e56778f21
|
4
|
+
data.tar.gz: 78b300250348617901cd7d2165b9fe0763ad49bf3327850ffdb2852d9dd55678
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 182c58aa6fa14ba3b25aaf89d11dcc8e1c461fda6d1fd92b34ab709f98eade14da02d6afd66b994c4ddb2191f07ae5c5db281b131c2dfa280ff6c94bdb616761
|
7
|
+
data.tar.gz: d6a1b2bb51890b68a9f33d9bd3aa799a55a7cc6135abdd614747ca2118bf6a139c50ae0874f5d99dcd5fa160d700c5a344bc5eb333bb4fe229549c4724bf74ee
|
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
|
+
<% Rosette.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/bin/rails
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path("..", __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path("../lib/rosette/engine", __dir__)
|
7
|
+
APP_PATH = File.expand_path("../spec/example_app/config/application", __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
11
|
+
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
|
12
|
+
|
13
|
+
require "rails"
|
14
|
+
|
15
|
+
require "action_controller/railtie"
|
16
|
+
require "action_view/railtie"
|
17
|
+
require "active_model/railtie"
|
18
|
+
require "active_record/railtie"
|
19
|
+
require "rails/engine/commands"
|
data/bin/rosette
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require File.join(Dir.pwd, 'config/environment')
|
6
|
+
rescue LoadError
|
7
|
+
puts <<~ERR
|
8
|
+
Could not load your Rails app
|
9
|
+
=============================
|
10
|
+
Rosette assumes you have a config/environment.rb file it can load. If this is the case, please
|
11
|
+
make sure you are using Rosette CLI from the root of your Rails application. Do not hesitate to
|
12
|
+
raise an issue if you need further assistance.
|
13
|
+
ERR
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rosette'
|
18
|
+
|
19
|
+
command = ARGV.shift
|
20
|
+
ARGV.clear
|
21
|
+
|
22
|
+
Rosette::CLI.run(command: command)
|
data/config/routes.rb
CHANGED
data/lib/rosette/cli.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rosette
|
4
|
+
class CLI
|
5
|
+
COMMANDS = %w[add remove read].freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(command:)
|
9
|
+
return display_help unless command.in?(COMMANDS)
|
10
|
+
|
11
|
+
ask_user_for_key
|
12
|
+
send(command)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def read
|
18
|
+
Rosette.available_locales.each do |locale|
|
19
|
+
translation = Manager.read(locale, @key)
|
20
|
+
puts "Translation for #{locale} is: #{translation}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add
|
25
|
+
Rosette.available_locales.each do |locale|
|
26
|
+
puts "Please enter #{locale} translation:"
|
27
|
+
Manager.create(locale, @key, gets.chomp)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove
|
32
|
+
Rosette.available_locales.each do |locale|
|
33
|
+
Manager.delete(locale, @key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ask_user_for_key
|
38
|
+
puts 'Please provide the key to translation:'
|
39
|
+
@key = gets.chomp
|
40
|
+
|
41
|
+
normalize_key!
|
42
|
+
end
|
43
|
+
|
44
|
+
def normalize_key!
|
45
|
+
Rosette.available_locales.each do |locale|
|
46
|
+
@key.delete_prefix!("#{locale}.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def display_help
|
51
|
+
puts <<~HELP
|
52
|
+
Usage: rosette [command]
|
53
|
+
|
54
|
+
Available commands:
|
55
|
+
|
56
|
+
read read translation from available locales
|
57
|
+
add add a translation to available locales
|
58
|
+
remove remove translation from available locales
|
59
|
+
|
60
|
+
For each command you must provide a key pointing to the translation.
|
61
|
+
It can begin with or without the locale. These are all valid keys:
|
62
|
+
|
63
|
+
fr.activemodel.errors.blank
|
64
|
+
en.activemodel.errors.blank
|
65
|
+
activemodel.errors.blank
|
66
|
+
|
67
|
+
HELP
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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
|
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,73 @@
|
|
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 read
|
21
|
+
stored_translations.dig(*@keys)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
new_translation = @keys.reverse.inject(@translation) { |v, k| { k => v } }
|
26
|
+
updated_translations = deep_merge(stored_translations, new_translation)
|
27
|
+
persist(updated_translations)
|
28
|
+
|
29
|
+
Manager.normalize!
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete
|
33
|
+
updated_translations = delete_translation(stored_translations, @keys)
|
34
|
+
persist(updated_translations)
|
35
|
+
|
36
|
+
Manager.normalize!
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_translation(translations, keys)
|
40
|
+
return translations.delete(keys.first) if keys.length == 1
|
41
|
+
|
42
|
+
head, *tail = keys
|
43
|
+
delete_translation(translations[head], tail)
|
44
|
+
|
45
|
+
translations
|
46
|
+
end
|
47
|
+
|
48
|
+
def persist(translations)
|
49
|
+
File.write(@file_path, translations.deep_stringify_keys.to_yaml)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stored_translations
|
53
|
+
YAML.safe_load(File.read(@file_path), symbolize_names: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def deep_merge(hash, other_hash)
|
57
|
+
hash.merge(other_hash) do |_key, this_val, other_val|
|
58
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
59
|
+
deep_merge(this_val, other_val)
|
60
|
+
else
|
61
|
+
other_val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def normalize(key)
|
67
|
+
key.split('.').map(&:to_sym)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.normalize!
|
71
|
+
I18n::Tasks::CLI.start(['normalize']) if Rosette.normalize
|
72
|
+
end
|
73
|
+
end
|
data/lib/rosette/version.rb
CHANGED
data/lib/rosette.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rosette/cli"
|
4
|
+
require "rosette/controller"
|
2
5
|
require "rosette/engine"
|
6
|
+
require "rosette/manager"
|
7
|
+
require "rosette/version"
|
8
|
+
|
9
|
+
require "i18n/tasks/cli"
|
10
|
+
require "yaml"
|
3
11
|
|
4
12
|
module Rosette
|
5
|
-
|
13
|
+
mattr_accessor :normalize
|
14
|
+
|
15
|
+
def self.available_locales
|
16
|
+
Rails.configuration.i18n.available_locales.map(&:to_s)
|
17
|
+
end
|
6
18
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rosette
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Platteeuw
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-18 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
|
@@ -41,7 +55,8 @@ dependencies:
|
|
41
55
|
description: Add missing translations from the interface of your application
|
42
56
|
email:
|
43
57
|
- alexplatteeuw@gmail.com
|
44
|
-
executables:
|
58
|
+
executables:
|
59
|
+
- rosette
|
45
60
|
extensions: []
|
46
61
|
extra_rdoc_files: []
|
47
62
|
files:
|
@@ -51,12 +66,19 @@ files:
|
|
51
66
|
- app/assets/config/rosette_manifest.js
|
52
67
|
- app/assets/stylesheets/rosette/application.css
|
53
68
|
- app/controllers/rosette/application_controller.rb
|
69
|
+
- app/controllers/rosette/translations_controller.rb
|
54
70
|
- app/helpers/rosette/application_helper.rb
|
55
71
|
- app/models/rosette/application_record.rb
|
56
72
|
- app/views/layouts/rosette/application.html.erb
|
73
|
+
- app/views/rosette/new.html.erb
|
74
|
+
- bin/rails
|
75
|
+
- bin/rosette
|
57
76
|
- config/routes.rb
|
58
77
|
- lib/rosette.rb
|
78
|
+
- lib/rosette/cli.rb
|
79
|
+
- lib/rosette/controller.rb
|
59
80
|
- lib/rosette/engine.rb
|
81
|
+
- lib/rosette/manager.rb
|
60
82
|
- lib/rosette/version.rb
|
61
83
|
- lib/tasks/rosette_tasks.rake
|
62
84
|
homepage: https://rubygems.org/gems/rosette
|