moirai 0.1.0 → 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/.github/workflows/test.yml +8 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +12 -1
- data/README.md +20 -2
- data/app/assets/config/moirai_manifest.js +0 -1
- data/app/assets/javascripts/moirai_translation_controller.js +32 -0
- data/app/controllers/moirai/translation_files_controller.rb +41 -16
- data/app/controllers/moirai/translations_controller.rb +7 -0
- data/app/models/moirai/change.rb +15 -0
- data/app/models/moirai/key_finder.rb +55 -0
- data/app/models/moirai/translation.rb +9 -5
- data/app/models/moirai/translation_dumper.rb +40 -13
- data/app/views/layouts/moirai/application.html.erb +0 -1
- data/app/views/moirai/translation_files/_form.html.erb +3 -3
- data/app/views/moirai/translation_files/index.html.erb +1 -1
- data/app/views/moirai/translation_files/show.html.erb +5 -5
- data/app/views/moirai/translations/index.html.erb +7 -0
- data/bin/check +3 -1
- data/config/routes.rb +2 -1
- data/lib/generators/moirai/install_generator.rb +1 -2
- data/lib/generators/moirai/migration_generator.rb +5 -1
- data/lib/generators/moirai/templates/make_moirai_translations_file_path_not_required.rb.erb +5 -0
- data/lib/i18n/backend/moirai.rb +1 -53
- data/lib/moirai/engine.rb +3 -6
- data/lib/moirai/pull_request_creator.rb +42 -33
- data/moirai.gemspec +1 -6
- metadata +9 -76
- data/app/assets/javascripts/moirai/application.js +0 -2
- data/app/assets/javascripts/moirai/controllers/moirai_translation_controller.js +0 -27
- data/app/assets/javascripts/moirai/stimulus/init.js +0 -5
- /data/lib/generators/moirai/templates/{migration.rb.erb → create_moirai_translations.rb.erb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cd2eae769961645bc1baa4295ed58bf261beb38a48e934c1d3d38dd083f033a
|
4
|
+
data.tar.gz: fd3dddd2520c7f06987d814fec2986c5e02ac113748d39a1117315296cc0aaf3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ae7c1af2f697ea2f7fad123f4c2451e3fa9ba86ffb048ae0b6846c2002bc532e50f762ecf000e32d76f6631d62ebd9f211288b4dca081e61e9d1bbcb05fd6ef
|
7
|
+
data.tar.gz: f113aa608adfe9b5fae4a073f2d23936bb30090b3fe9d544dfc7d9421663244a78e3350b7218f988fa9c413fe6aef6a6ab154dd79cb5d0160dd026205bdac30d
|
data/.github/workflows/test.yml
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
name: Test & lint
|
2
|
-
on: [push
|
2
|
+
on: [push]
|
3
3
|
|
4
4
|
env:
|
5
5
|
RAILS_ENV: test
|
6
6
|
PGHOST: localhost
|
7
7
|
PGUSER: postgres
|
8
|
-
|
8
|
+
MOIRAI_GITHUB_REPO_NAME: ${{ secrets.MOIRAI_GITHUB_REPO_NAME }}
|
9
|
+
MOIRAI_GITHUB_ACCESS_TOKEN: ${{ secrets.MOIRAI_GITHUB_ACCESS_TOKEN }}
|
9
10
|
jobs:
|
10
11
|
tests:
|
11
12
|
name: Test
|
@@ -14,7 +15,7 @@ jobs:
|
|
14
15
|
fail-fast: false
|
15
16
|
matrix:
|
16
17
|
os: [ubuntu-latest]
|
17
|
-
ruby: [3.
|
18
|
+
ruby: [3.3]
|
18
19
|
|
19
20
|
runs-on: ${{ matrix.os }}
|
20
21
|
|
@@ -32,7 +33,10 @@ jobs:
|
|
32
33
|
run: bundle install --jobs 4 --retry 3
|
33
34
|
|
34
35
|
- name: Run tests
|
35
|
-
run:
|
36
|
+
run: bundle exec rails test
|
37
|
+
|
38
|
+
- name: Run system tests
|
39
|
+
run: bundle exec rails test:system
|
36
40
|
|
37
41
|
|
38
42
|
lint:
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
|
+
## Unreleased
|
2
|
+
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
* Support for strings coming from gems ([@coorasse][])
|
6
|
+
* Support for new strings (not yet translated) ([@coorasse][])
|
7
|
+
|
8
|
+
## 0.1.1
|
9
|
+
|
10
|
+
* Review Stimulus controller ([@coorasse][])
|
11
|
+
|
1
12
|
## 0.1.0
|
2
13
|
|
3
14
|
* Gem structure created ([@oliveranthony17][])
|
15
|
+
* Database tables created ([@oliveranthony17][])
|
16
|
+
* Pull request creation ([@oliveranthony17][])
|
17
|
+
* Dummy app for tests ([@coorasse][])
|
18
|
+
* CRUD for translations ([@CuddlyBunion341][])
|
19
|
+
* Inline editing ([@CuddlyBunion341][])
|
4
20
|
|
5
21
|
[@coorasse]: https://github.com/coorasse
|
6
22
|
|
data/Gemfile
CHANGED
@@ -4,14 +4,25 @@ source "https://rubygems.org"
|
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
+
gem "rails", "~> 7.2.0"
|
8
|
+
gem "octokit", ">= 4.0"
|
9
|
+
gem "importmap-rails"
|
10
|
+
gem "stimulus-rails"
|
11
|
+
|
7
12
|
group :development, :test do
|
8
13
|
gem "puma"
|
9
14
|
gem "sqlite3"
|
10
|
-
gem "minitest"
|
11
15
|
gem "rake"
|
16
|
+
gem "dotenv"
|
17
|
+
gem "minitest"
|
12
18
|
gem "standard"
|
13
19
|
gem "appraisal"
|
14
20
|
gem "better_errors"
|
15
21
|
gem "binding_of_caller"
|
16
22
|
gem "sprockets-rails"
|
17
23
|
end
|
24
|
+
|
25
|
+
group :test do
|
26
|
+
gem "capybara"
|
27
|
+
gem "selenium-webdriver"
|
28
|
+
end
|
data/README.md
CHANGED
@@ -46,6 +46,7 @@ Open a file, change the value of translations, and press ENTER to update the tra
|
|
46
46
|
### Inline editing
|
47
47
|
|
48
48
|
By default, inline editing is disabled. To enable it, set the `moirai=true` query parameter in the URL.
|
49
|
+
|
49
50
|
If you want to only allow specific users to perform inline editing, you can override the `moirai_edit_enabled?` method in your application helper.
|
50
51
|
|
51
52
|
```ruby
|
@@ -56,6 +57,18 @@ module ApplicationHelper
|
|
56
57
|
end
|
57
58
|
```
|
58
59
|
|
60
|
+
You also need to have the moirai_translations_controller.js Stimulus Controller initialized.
|
61
|
+
|
62
|
+
If you use importmaps:
|
63
|
+
|
64
|
+
Pin the controller in `config/importmap.rb`
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
pin "controllers/moirai_translation_controller", to: "moirai_translation_controller.js"
|
68
|
+
```
|
69
|
+
|
70
|
+
If you’re unsure about all the possible configuration options, you can simply copy and paste the stimulus controller into your app as a fallback.
|
71
|
+
|
59
72
|
### Automatic PR creation with Octokit (**optional**)
|
60
73
|
|
61
74
|
If you would like Moirai to automatically create a pull request on GitHub to keep translations synchronized with the codebase,
|
@@ -153,6 +166,12 @@ end
|
|
153
166
|
|
154
167
|
4. Set your environment variables using the newly created `.env` file.
|
155
168
|
|
169
|
+
You will need a repository to test against and a token. Generate a new Fine-GRained Personal access token and give the necessary permissions to your repository.
|
170
|
+
See the image below as an example:
|
171
|
+
|
172
|
+
![](docs/github_settings.png)
|
173
|
+
|
174
|
+
|
156
175
|
5. Run the tests:
|
157
176
|
```bash
|
158
177
|
bin/check
|
@@ -169,8 +188,7 @@ end
|
|
169
188
|
* Support for interpolation
|
170
189
|
* Support for count variants
|
171
190
|
* Better inline editing tool
|
172
|
-
*
|
173
|
-
* Support for translations and strings coming from other gems
|
191
|
+
* Support for fallbacks: it should detect when a fallback string is in use and prevent attempts to override its value.
|
174
192
|
|
175
193
|
## License
|
176
194
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class MoiraiTranslationController extends Controller {
|
4
|
+
static values = {
|
5
|
+
key: String,
|
6
|
+
locale: String
|
7
|
+
}
|
8
|
+
|
9
|
+
click(event) {
|
10
|
+
event.preventDefault()
|
11
|
+
}
|
12
|
+
|
13
|
+
submit(event) {
|
14
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
|
15
|
+
|
16
|
+
fetch('/moirai/translation_files', {
|
17
|
+
method: 'POST',
|
18
|
+
headers: {
|
19
|
+
'X-CSRF-Token': csrfToken,
|
20
|
+
'Content-Type': 'application/json',
|
21
|
+
'Accept': 'application/json'
|
22
|
+
},
|
23
|
+
body: JSON.stringify({
|
24
|
+
translation: {
|
25
|
+
key: this.keyValue,
|
26
|
+
locale: this.localeValue,
|
27
|
+
value: event.target.innerText
|
28
|
+
}
|
29
|
+
})
|
30
|
+
});
|
31
|
+
}
|
32
|
+
}
|
@@ -14,13 +14,13 @@ module Moirai
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def show
|
17
|
-
@translation_keys = @file_handler.parse_file(@
|
17
|
+
@translation_keys = @file_handler.parse_file(@file_path)
|
18
|
+
@locale = @file_handler.get_first_key(@file_path)
|
19
|
+
@translations = Moirai::Translation.by_file_path(@file_path)
|
18
20
|
end
|
19
21
|
|
20
22
|
def create_or_update
|
21
|
-
if (translation = Translation.find_by(
|
22
|
-
key: translation_params[:key],
|
23
|
-
locale: @file_handler.get_first_key(translation_params[:file_path])))
|
23
|
+
if (translation = Translation.find_by(key: translation_params[:key], locale: translation_params[:locale]))
|
24
24
|
handle_update(translation)
|
25
25
|
else
|
26
26
|
handle_create
|
@@ -28,7 +28,7 @@ module Moirai
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def open_pr
|
31
|
-
flash.notice = "I created an amazing
|
31
|
+
flash.notice = "I created an amazing Pull Request"
|
32
32
|
changes = Moirai::TranslationDumper.new.call
|
33
33
|
Moirai::PullRequestCreator.new.create_pull_request(changes)
|
34
34
|
redirect_back_or_to(root_path)
|
@@ -37,8 +37,7 @@ module Moirai
|
|
37
37
|
private
|
38
38
|
|
39
39
|
def handle_update(translation)
|
40
|
-
|
41
|
-
if translation_from_file[translation.key] == translation_params[:value] || translation_params[:value].blank?
|
40
|
+
if translation_params[:value].blank? || translation_same_as_current?
|
42
41
|
translation.destroy
|
43
42
|
flash.notice = "Translation #{translation.key} was successfully deleted."
|
44
43
|
redirect_to_translation_file(translation.file_path)
|
@@ -51,26 +50,36 @@ module Moirai
|
|
51
50
|
flash.alert = translation.errors.full_messages.join(", ")
|
52
51
|
end
|
53
52
|
|
54
|
-
|
53
|
+
success_response(translation)
|
55
54
|
end
|
56
55
|
|
57
56
|
def handle_create
|
58
|
-
|
59
|
-
if translation_from_file[translation_params[:key]] == translation_params[:value]
|
57
|
+
if translation_same_as_current?
|
60
58
|
flash.alert = "Translation #{translation_params[:key]} already exists."
|
61
|
-
|
59
|
+
redirect_back_or_to moirai_translation_files_path, status: :unprocessable_entity
|
62
60
|
return
|
63
61
|
end
|
64
62
|
|
65
63
|
translation = Translation.new(translation_params)
|
66
|
-
|
64
|
+
|
67
65
|
if translation.save
|
68
66
|
flash.notice = "Translation #{translation.key} was successfully created."
|
67
|
+
success_response(translation)
|
69
68
|
else
|
70
69
|
flash.alert = translation.errors.full_messages.join(", ")
|
70
|
+
redirect_back_or_to moirai_translation_files_path, status: :unprocessable_entity
|
71
71
|
end
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
+
def success_response(translation)
|
75
|
+
respond_to do |format|
|
76
|
+
format.json do
|
77
|
+
render json: {}
|
78
|
+
end
|
79
|
+
format.all do
|
80
|
+
redirect_to_translation_file(translation.file_path)
|
81
|
+
end
|
82
|
+
end
|
74
83
|
end
|
75
84
|
|
76
85
|
def redirect_to_translation_file(file_path)
|
@@ -78,16 +87,32 @@ module Moirai
|
|
78
87
|
end
|
79
88
|
|
80
89
|
def set_translation_file
|
81
|
-
@file_path = @file_handler.file_hashes[params[:
|
82
|
-
|
90
|
+
@file_path = @file_handler.file_hashes[params[:hashed_file_path]]
|
91
|
+
if @file_path.nil?
|
92
|
+
flash.alert = "File not found"
|
93
|
+
redirect_to moirai_translation_files_path, status: :not_found
|
94
|
+
end
|
83
95
|
end
|
84
96
|
|
85
97
|
def translation_params
|
86
|
-
params.require(:translation).permit(:key, :locale, :value
|
98
|
+
params.require(:translation).permit(:key, :locale, :value)
|
87
99
|
end
|
88
100
|
|
89
101
|
def load_file_handler
|
90
102
|
@file_handler = Moirai::TranslationFileHandler.new
|
91
103
|
end
|
104
|
+
|
105
|
+
# TODO: to resolve the last point of the TODOs we could look at the current translation (without moirai)
|
106
|
+
# I quickly tried but I need to use the original backend instead of the moirai one
|
107
|
+
# The problem is that if we set a value that is the same as currently being used via fallback,
|
108
|
+
# it will create an entry in the database, and afterwards will try to add it in the PR, which we don't want.
|
109
|
+
def translation_same_as_current?
|
110
|
+
file_paths = KeyFinder.new.file_paths_for(translation_params[:key], locale: translation_params[:locale])
|
111
|
+
|
112
|
+
return false if file_paths.empty?
|
113
|
+
return false unless file_paths.all? { |file_path| File.exist?(file_path) }
|
114
|
+
|
115
|
+
translation_params[:value] == @file_handler.parse_file(file_paths.first)[translation_params[:key]]
|
116
|
+
end
|
92
117
|
end
|
93
118
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Prepares a certain file to be changefd in a Pull Request.
|
4
|
+
# It takes care of adjusting the file_path and content
|
5
|
+
module Moirai
|
6
|
+
class Change
|
7
|
+
attr_reader :file_path, :content
|
8
|
+
|
9
|
+
def initialize(file_path, content)
|
10
|
+
@file_path = file_path
|
11
|
+
@file_path = file_path.to_s.start_with?("./") ? file_path : "./#{file_path}"
|
12
|
+
@content = content.to_s.end_with?("\n") ? content : "#{content}\n"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moirai
|
4
|
+
class KeyFinder
|
5
|
+
include I18n::Backend::Base
|
6
|
+
|
7
|
+
# Mutex to ensure that concurrent translations loading will be thread-safe
|
8
|
+
MUTEX = Mutex.new
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
load_translations
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO: remove locale default
|
15
|
+
# Returns all the file_paths where the key is found, including gems.
|
16
|
+
def file_paths_for(key, locale: I18n.locale)
|
17
|
+
return [] if key.blank?
|
18
|
+
|
19
|
+
locale ||= I18n.locale
|
20
|
+
moirai_translations[locale.to_sym].select do |_filename, data|
|
21
|
+
data.dig(*key.split(".")).present?
|
22
|
+
end.map { |k, _| k }.sort { |file_path| file_path.start_with?(Rails.root.to_s) ? 0 : 1 }
|
23
|
+
end
|
24
|
+
|
25
|
+
def store_moirai_translations(filename, locale, data, options)
|
26
|
+
moirai_translations[locale] ||= Concurrent::Hash.new
|
27
|
+
|
28
|
+
locale = locale.to_sym
|
29
|
+
moirai_translations[locale] ||= Concurrent::Hash.new
|
30
|
+
moirai_translations[locale][filename] = data.with_indifferent_access
|
31
|
+
end
|
32
|
+
|
33
|
+
def moirai_translations(do_init: false)
|
34
|
+
@moirai_translations ||= Concurrent::Hash.new do |h, k|
|
35
|
+
MUTEX.synchronize do
|
36
|
+
h[k] = Concurrent::Hash.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_file(filename)
|
42
|
+
type = File.extname(filename).tr(".", "").downcase
|
43
|
+
raise I18n::UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
|
44
|
+
data, keys_symbolized = send(:"load_#{type}", filename)
|
45
|
+
unless data.is_a?(Hash)
|
46
|
+
raise I18n::InvalidLocaleData.new(filename, "expects it to return a hash, but does not")
|
47
|
+
end
|
48
|
+
data.each do |locale, d|
|
49
|
+
store_moirai_translations(filename, locale, d || {}, skip_symbolize_keys: keys_symbolized)
|
50
|
+
end
|
51
|
+
|
52
|
+
data
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -2,13 +2,17 @@
|
|
2
2
|
|
3
3
|
module Moirai
|
4
4
|
class Translation < Moirai::ApplicationRecord
|
5
|
-
validates_presence_of :key, :locale, :
|
6
|
-
validate :file_path_must_exist
|
5
|
+
validates_presence_of :key, :locale, :value
|
7
6
|
|
8
|
-
|
7
|
+
# what if the key is present in multiple file_paths?
|
8
|
+
def file_path
|
9
|
+
@key_finder ||= KeyFinder.new
|
10
|
+
@key_finder.file_paths_for(key, locale: locale).first
|
11
|
+
end
|
9
12
|
|
10
|
-
def
|
11
|
-
|
13
|
+
def self.by_file_path(file_path)
|
14
|
+
key_finder = KeyFinder.new
|
15
|
+
all.select { |translation| key_finder.file_paths_for(translation.key, locale: translation.locale).include?(file_path) }
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -1,24 +1,51 @@
|
|
1
1
|
module Moirai
|
2
2
|
class TranslationDumper
|
3
|
+
def initialize
|
4
|
+
@key_finder = KeyFinder.new
|
5
|
+
end
|
6
|
+
|
7
|
+
# @return Array[Moirai::Change]
|
3
8
|
def call
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
translations_by_file_path = group_translations_by_file_path
|
10
|
+
changes = []
|
11
|
+
translations_by_file_path.each do |file_path, translations|
|
12
|
+
relative_file_path = Pathname.new(file_path).relative_path_from(Rails.root)
|
13
|
+
changes << Change.new(relative_file_path, get_updated_file_contents(file_path, translations))
|
14
|
+
end
|
15
|
+
changes
|
16
|
+
end
|
17
|
+
|
18
|
+
def best_file_path_for(key, locale)
|
19
|
+
file_paths = @key_finder.file_paths_for(key, locale: locale)
|
20
|
+
file_paths.filter! { |p| p.start_with?(Rails.root.to_s) }
|
21
|
+
if file_paths.any?
|
22
|
+
file_paths.first
|
23
|
+
elsif key.split(".").size > 1
|
24
|
+
parent_key = key.split(".")[0..-2].join(".")
|
25
|
+
best_file_path_for(parent_key, locale)
|
26
|
+
else
|
27
|
+
"./config/locales/#{locale}.yml"
|
28
|
+
end
|
15
29
|
end
|
16
30
|
|
17
31
|
private
|
18
32
|
|
19
|
-
def
|
20
|
-
|
33
|
+
def group_translations_by_file_path
|
34
|
+
translations_grouped_by_file_path = {}
|
35
|
+
Moirai::Translation.order(created_at: :asc).each do |translation|
|
36
|
+
file_path = best_file_path_for(translation.key, translation.locale)
|
37
|
+
absolute_file_path = File.expand_path(file_path, Rails.root)
|
38
|
+
|
39
|
+
# skip file paths that don't belong to the project
|
40
|
+
next unless absolute_file_path.to_s.start_with?(Rails.root.to_s)
|
41
|
+
|
42
|
+
translations_grouped_by_file_path[absolute_file_path] ||= []
|
43
|
+
translations_grouped_by_file_path[absolute_file_path] << translation
|
44
|
+
end
|
45
|
+
translations_grouped_by_file_path
|
46
|
+
end
|
21
47
|
|
48
|
+
def get_updated_file_contents(file_path, translations)
|
22
49
|
yaml = YAML.load_file(file_path)
|
23
50
|
|
24
51
|
translations.each do |translation|
|
@@ -6,7 +6,6 @@
|
|
6
6
|
<%= csp_meta_tag %>
|
7
7
|
|
8
8
|
<%= stylesheet_link_tag "moirai/application", media: "all" %>
|
9
|
-
<%= javascript_include_tag "moirai/application", "data-turoblinks-track": "reload", type: "module" %>
|
10
9
|
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
11
10
|
<link rel="icon" href="data:image/png;base64,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">
|
12
11
|
</head>
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<span
|
2
2
|
contenteditable
|
3
|
-
data-action="blur->moirai-translation#submit click->moirai-translation#click"
|
3
|
+
data-action="blur->moirai-translation#submit click->moirai-translation#click"
|
4
4
|
style="border: 1px dashed #1d9f74; min-width: 30px; display: inline-block;"
|
5
|
-
data-key="<%= key %>"
|
6
|
-
data-
|
5
|
+
data-moirai-translation-key-value="<%= key %>"
|
6
|
+
data-moirai-translation-locale-value="<%= locale %>"
|
7
7
|
data-controller="moirai-translation">
|
8
8
|
<%= value %>
|
9
9
|
</span>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<h1>Translation files</h1>
|
3
3
|
|
4
4
|
<% if Moirai::PullRequestCreator.available? %>
|
5
|
-
<%= button_to "Create or update
|
5
|
+
<%= button_to "Create or update Pull Request", moirai_open_pr_path %>
|
6
6
|
<% else %>
|
7
7
|
<p>PR creation is not available. Add the gem octokit to your gemfile to enable this feature</p>
|
8
8
|
<% end %>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
</thead>
|
14
14
|
<tbody>
|
15
15
|
<% @translation_keys.each do |key, value| %>
|
16
|
-
<% translation =
|
16
|
+
<% translation = @translations.find { |t| t.key == key } %>
|
17
17
|
|
18
18
|
<tr>
|
19
19
|
<td>
|
@@ -24,10 +24,10 @@
|
|
24
24
|
<% end %>
|
25
25
|
</td>
|
26
26
|
<td>
|
27
|
-
<%= form_for Moirai::Translation.new, url: moirai_create_or_update_translation_path do |f| %>
|
28
|
-
<%= f.hidden_field :key
|
29
|
-
<%= f.hidden_field :
|
30
|
-
<%= f.text_field :value
|
27
|
+
<%= form_for translation&.presence || Moirai::Translation.new(key: key, locale: @locale, value: value), url: moirai_create_or_update_translation_path, method: :post do |f| %>
|
28
|
+
<%= f.hidden_field :key %>
|
29
|
+
<%= f.hidden_field :locale %>
|
30
|
+
<%= f.text_field :value %>
|
31
31
|
<%= f.submit 'Update', style: 'display: none;' %>
|
32
32
|
<% end %>
|
33
33
|
</td>
|
data/bin/check
CHANGED
data/config/routes.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
Moirai::Engine.routes.draw do
|
4
4
|
root to: "translation_files#index"
|
5
5
|
|
6
|
-
resources :
|
6
|
+
resources :translations, only: %i[index]
|
7
|
+
resources :translation_files, only: %i[index show], as: "moirai_translation_files", param: :hashed_file_path
|
7
8
|
post "/translation_files/open_pr", to: "translation_files#open_pr", as: "moirai_open_pr"
|
8
9
|
post "/translation_files", to: "translation_files#create_or_update", as: "moirai_create_or_update_translation"
|
9
10
|
end
|
@@ -11,7 +11,11 @@ module Moirai
|
|
11
11
|
source_root File.expand_path("templates", __dir__)
|
12
12
|
|
13
13
|
def create_migration_file
|
14
|
-
migration_template "
|
14
|
+
migration_template "create_moirai_translations.rb.erb", "db/migrate/create_moirai_translations.rb", migration_version: migration_version
|
15
|
+
end
|
16
|
+
|
17
|
+
def file_path_migration_file
|
18
|
+
migration_template "make_moirai_translations_file_path_not_required.rb.erb", "db/migrate/make_moirai_translations_file_path_not_required.rb", migration_version: migration_version
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
data/lib/i18n/backend/moirai.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module I18n
|
2
2
|
module Backend
|
3
|
-
class Moirai < I18n::Backend::Simple
|
3
|
+
class Moirai < I18n::Backend::Simple
|
4
4
|
# TODO: mega inefficient. we don't want to perform a SQL query for each key!
|
5
5
|
def translate(locale, key, options = EMPTY_HASH)
|
6
6
|
overridden_translation = ::Moirai::Translation.find_by(locale: locale, key: key)
|
@@ -8,58 +8,6 @@ module I18n
|
|
8
8
|
overridden_translation.value
|
9
9
|
end
|
10
10
|
end
|
11
|
-
|
12
|
-
def store_moirai_translations(filename, locale, data, options)
|
13
|
-
moirai_translations[locale] ||= Concurrent::Hash.new
|
14
|
-
flatten_data = flatten_hash(filename, data)
|
15
|
-
flatten_data = Utils.deep_symbolize_keys(flatten_data) unless options.fetch(:skip_symbolize_keys, false)
|
16
|
-
Utils.deep_merge!(moirai_translations[locale], flatten_data)
|
17
|
-
end
|
18
|
-
|
19
|
-
def moirai_translations(do_init: false)
|
20
|
-
@moirai_translations ||= Concurrent::Hash.new do |h, k|
|
21
|
-
MUTEX.synchronize do
|
22
|
-
h[k] = Concurrent::Hash.new
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def flatten_hash(filename, hash, parent_key = "", result = {})
|
28
|
-
hash.each do |key, value|
|
29
|
-
new_key = parent_key.empty? ? key.to_s : "#{parent_key}.#{key}"
|
30
|
-
case value
|
31
|
-
when Hash
|
32
|
-
flatten_hash(filename, value, new_key, result)
|
33
|
-
when Array
|
34
|
-
value.each_with_index do |item, index|
|
35
|
-
array_key = "#{new_key}.#{index}"
|
36
|
-
if item.is_a?(Hash)
|
37
|
-
flatten_hash(filename, item, array_key, result)
|
38
|
-
else
|
39
|
-
result[array_key] = filename
|
40
|
-
end
|
41
|
-
end
|
42
|
-
else
|
43
|
-
result[new_key] = filename
|
44
|
-
end
|
45
|
-
end
|
46
|
-
result
|
47
|
-
end
|
48
|
-
|
49
|
-
def load_file(filename)
|
50
|
-
type = File.extname(filename).tr(".", "").downcase
|
51
|
-
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
|
52
|
-
data, keys_symbolized = send(:"load_#{type}", filename)
|
53
|
-
unless data.is_a?(Hash)
|
54
|
-
raise InvalidLocaleData.new(filename, "expects it to return a hash, but does not")
|
55
|
-
end
|
56
|
-
data.each do |locale, d|
|
57
|
-
store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized)
|
58
|
-
store_moirai_translations(filename, locale, d || {}, skip_symbolize_keys: keys_symbolized)
|
59
|
-
end
|
60
|
-
|
61
|
-
data
|
62
|
-
end
|
63
11
|
end
|
64
12
|
end
|
65
13
|
end
|
data/lib/moirai/engine.rb
CHANGED
@@ -9,9 +9,7 @@ module Moirai
|
|
9
9
|
end
|
10
10
|
|
11
11
|
config.after_initialize do
|
12
|
-
|
13
|
-
moirai_backend.eager_load!
|
14
|
-
I18n.backend = I18n::Backend::Chain.new(moirai_backend, I18n.backend)
|
12
|
+
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::Moirai.new, I18n.backend)
|
15
13
|
end
|
16
14
|
|
17
15
|
# TODO: how to do this without rewriting the entire method?
|
@@ -25,12 +23,11 @@ module Moirai
|
|
25
23
|
value = original_translate(key, **options)
|
26
24
|
|
27
25
|
if moirai_edit_enabled?
|
28
|
-
|
29
|
-
file_path = moirai_translations[I18n.locale][scope_key_by_partial(key)]
|
26
|
+
@key_finder ||= Moirai::KeyFinder.new
|
30
27
|
|
31
28
|
render(partial: "moirai/translation_files/form",
|
32
29
|
locals: {key: scope_key_by_partial(key),
|
33
|
-
|
30
|
+
locale: I18n.locale,
|
34
31
|
value: value})
|
35
32
|
else
|
36
33
|
value
|
@@ -1,41 +1,43 @@
|
|
1
1
|
class Moirai::PullRequestCreator
|
2
|
-
BRANCH_NAME = "moirai-translations"
|
3
|
-
|
4
2
|
def self.available?
|
5
|
-
defined?(Octokit)
|
3
|
+
!!defined?(Octokit)
|
6
4
|
end
|
7
5
|
|
6
|
+
BRANCH_PREFIX = "moirai-translations-"
|
7
|
+
|
8
|
+
attr_reader :github_repo_name, :github_access_token, :github_client, :github_repository, :branch_name
|
9
|
+
|
8
10
|
def initialize
|
9
11
|
@github_repo_name = ENV["MOIRAI_GITHUB_REPO_NAME"] || Rails.application.credentials.dig(:moirai, :github_repo_name)
|
10
12
|
@github_access_token = ENV["MOIRAI_GITHUB_ACCESS_TOKEN"] || Rails.application.credentials.dig(:moirai, :github_access_token)
|
11
|
-
@
|
12
|
-
|
13
|
-
)
|
13
|
+
@github_client = Octokit::Client.new(access_token: github_access_token)
|
14
|
+
@github_repository = github_client.repo(github_repo_name)
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
# @param changes Array[Moirai::Change]
|
18
|
+
def create_pull_request(changes)
|
19
|
+
@branch_name = "#{BRANCH_PREFIX}#{Time.current.strftime("%F-%H-%M-%S")}-#{rand(1000..9999)}"
|
20
|
+
default_branch = github_repository.default_branch
|
19
21
|
|
20
|
-
if
|
21
|
-
|
22
|
+
if moirai_branch_exists?
|
23
|
+
Rails.logger.debug { "Branch #{branch_name} already exists - the branch will be updated with the new changes" }
|
22
24
|
else
|
23
|
-
|
24
|
-
default_branch_ref = @
|
25
|
+
Rails.logger.debug { "Branch #{branch_name} does not exist - creating branch" }
|
26
|
+
default_branch_ref = @github_client.ref(@github_repo_name, "heads/#{default_branch}")
|
25
27
|
latest_commit_sha = default_branch_ref.object.sha
|
26
28
|
|
27
|
-
@
|
29
|
+
@github_client.create_ref(@github_repo_name, "heads/#{branch_name}", latest_commit_sha)
|
28
30
|
end
|
29
31
|
|
30
|
-
|
31
|
-
update_file(
|
32
|
+
changes.each do |change|
|
33
|
+
update_file(change.file_path, change.content)
|
32
34
|
end
|
33
35
|
|
34
|
-
unless
|
35
|
-
pull_request = @
|
36
|
+
unless existing_open_pull_request.present?
|
37
|
+
pull_request = @github_client.create_pull_request(
|
36
38
|
@github_repo_name,
|
37
39
|
default_branch,
|
38
|
-
|
40
|
+
branch_name,
|
39
41
|
"Adding new content by Moirai",
|
40
42
|
"BODY - This is a pull request created by Moirai"
|
41
43
|
)
|
@@ -44,40 +46,47 @@ class Moirai::PullRequestCreator
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
@
|
49
|
+
def moirai_branch_exists?
|
50
|
+
@github_client.ref(@github_repo_name, "heads/#{branch_name}")
|
49
51
|
true
|
50
52
|
rescue Octokit::NotFound
|
51
53
|
false
|
52
54
|
end
|
53
55
|
|
56
|
+
def existing_open_pull_request
|
57
|
+
@github_client.pull_requests(@github_repo_name).find do |pull_request|
|
58
|
+
pull_request.head.ref.start_with?(BRANCH_PREFIX) && (pull_request.state == "open")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def cleanup
|
63
|
+
pr = existing_open_pull_request
|
64
|
+
@github_client.close_pull_request(@github_repo_name, pr.number)
|
65
|
+
@github_client.delete_branch(@github_repo_name, pr.head.ref)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
54
70
|
def update_file(path, content)
|
55
71
|
# TODO: check what happens if branch exists
|
56
|
-
|
57
|
-
file = @client.contents(@github_repo_name, path: path, ref: BRANCH_NAME)
|
72
|
+
file = @github_client.contents(@github_repo_name, path: path, ref: branch_name)
|
58
73
|
file_sha = file.sha
|
59
74
|
|
60
|
-
@
|
75
|
+
@github_client.update_contents(
|
61
76
|
@github_repo_name,
|
62
77
|
path,
|
63
78
|
"Updating translations for #{path} by Moirai",
|
64
79
|
file_sha,
|
65
80
|
content,
|
66
|
-
branch:
|
81
|
+
branch: branch_name
|
67
82
|
)
|
68
83
|
rescue Octokit::NotFound
|
69
|
-
@
|
84
|
+
@github_client.create_contents(
|
70
85
|
@github_repo_name,
|
71
86
|
path,
|
72
87
|
"Creating translations for #{path} by Moirai",
|
73
88
|
content,
|
74
|
-
branch:
|
89
|
+
branch: branch_name
|
75
90
|
)
|
76
91
|
end
|
77
|
-
|
78
|
-
def pull_request_exists?
|
79
|
-
@client.pull_requests(@github_repo_name).any? do |pull_request|
|
80
|
-
pull_request.head.ref == BRANCH_NAME
|
81
|
-
end
|
82
|
-
end
|
83
92
|
end
|
data/moirai.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "moirai"
|
5
|
-
spec.version = "0.
|
5
|
+
spec.version = "0.2.0"
|
6
6
|
spec.authors = ["Alessandro Rodi", "Oliver Anthony", "Daniel Bengl"]
|
7
7
|
spec.email = %w[alessandro.rodi@renuo.ch oliver.anthony@renuo.ch daniel.bengl@renuo.ch]
|
8
8
|
|
@@ -25,11 +25,6 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.required_ruby_version = ">= 2.7.0"
|
27
27
|
spec.add_dependency "rails", ">= 6.1"
|
28
|
-
spec.add_development_dependency "octokit", ">= 4.0"
|
29
|
-
spec.add_development_dependency "capybara"
|
30
|
-
spec.add_development_dependency "selenium-webdriver"
|
31
|
-
spec.add_development_dependency "rails", "~> 7.2.0"
|
32
|
-
spec.add_development_dependency "dotenv"
|
33
28
|
|
34
29
|
spec.license = "MIT"
|
35
30
|
spec.metadata["rubygems_mfa_required"] = "true"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moirai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Rodi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-11-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -26,76 +26,6 @@ dependencies:
|
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '6.1'
|
29
|
-
- !ruby/object:Gem::Dependency
|
30
|
-
name: octokit
|
31
|
-
requirement: !ruby/object:Gem::Requirement
|
32
|
-
requirements:
|
33
|
-
- - ">="
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: '4.0'
|
36
|
-
type: :development
|
37
|
-
prerelease: false
|
38
|
-
version_requirements: !ruby/object:Gem::Requirement
|
39
|
-
requirements:
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: '4.0'
|
43
|
-
- !ruby/object:Gem::Dependency
|
44
|
-
name: capybara
|
45
|
-
requirement: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - ">="
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '0'
|
50
|
-
type: :development
|
51
|
-
prerelease: false
|
52
|
-
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
requirements:
|
54
|
-
- - ">="
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '0'
|
57
|
-
- !ruby/object:Gem::Dependency
|
58
|
-
name: selenium-webdriver
|
59
|
-
requirement: !ruby/object:Gem::Requirement
|
60
|
-
requirements:
|
61
|
-
- - ">="
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: '0'
|
64
|
-
type: :development
|
65
|
-
prerelease: false
|
66
|
-
version_requirements: !ruby/object:Gem::Requirement
|
67
|
-
requirements:
|
68
|
-
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: '0'
|
71
|
-
- !ruby/object:Gem::Dependency
|
72
|
-
name: rails
|
73
|
-
requirement: !ruby/object:Gem::Requirement
|
74
|
-
requirements:
|
75
|
-
- - "~>"
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 7.2.0
|
78
|
-
type: :development
|
79
|
-
prerelease: false
|
80
|
-
version_requirements: !ruby/object:Gem::Requirement
|
81
|
-
requirements:
|
82
|
-
- - "~>"
|
83
|
-
- !ruby/object:Gem::Version
|
84
|
-
version: 7.2.0
|
85
|
-
- !ruby/object:Gem::Dependency
|
86
|
-
name: dotenv
|
87
|
-
requirement: !ruby/object:Gem::Requirement
|
88
|
-
requirements:
|
89
|
-
- - ">="
|
90
|
-
- !ruby/object:Gem::Version
|
91
|
-
version: '0'
|
92
|
-
type: :development
|
93
|
-
prerelease: false
|
94
|
-
version_requirements: !ruby/object:Gem::Requirement
|
95
|
-
requirements:
|
96
|
-
- - ">="
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
version: '0'
|
99
29
|
description: |-
|
100
30
|
This gem allows you to manage translation strings in real time,
|
101
31
|
viewing the live changes in the browser, with the changes then converted to a PR opened on the repository.
|
@@ -117,17 +47,18 @@ files:
|
|
117
47
|
- Rakefile
|
118
48
|
- app/assets/config/moirai_manifest.js
|
119
49
|
- app/assets/images/moirai/.keep
|
120
|
-
- app/assets/javascripts/
|
121
|
-
- app/assets/javascripts/moirai/controllers/moirai_translation_controller.js
|
122
|
-
- app/assets/javascripts/moirai/stimulus/init.js
|
50
|
+
- app/assets/javascripts/moirai_translation_controller.js
|
123
51
|
- app/assets/stylesheets/moirai/application.css
|
124
52
|
- app/controllers/concerns/.keep
|
125
53
|
- app/controllers/moirai/application_controller.rb
|
126
54
|
- app/controllers/moirai/translation_files_controller.rb
|
55
|
+
- app/controllers/moirai/translations_controller.rb
|
127
56
|
- app/helpers/moirai/application_helper.rb
|
128
57
|
- app/jobs/moirai/application_job.rb
|
129
58
|
- app/models/concerns/.keep
|
130
59
|
- app/models/moirai/application_record.rb
|
60
|
+
- app/models/moirai/change.rb
|
61
|
+
- app/models/moirai/key_finder.rb
|
131
62
|
- app/models/moirai/translation.rb
|
132
63
|
- app/models/moirai/translation_dumper.rb
|
133
64
|
- app/models/moirai/translation_file_handler.rb
|
@@ -135,6 +66,7 @@ files:
|
|
135
66
|
- app/views/moirai/translation_files/_form.html.erb
|
136
67
|
- app/views/moirai/translation_files/index.html.erb
|
137
68
|
- app/views/moirai/translation_files/show.html.erb
|
69
|
+
- app/views/moirai/translations/index.html.erb
|
138
70
|
- bin/check
|
139
71
|
- bin/console
|
140
72
|
- bin/fastcheck
|
@@ -143,7 +75,8 @@ files:
|
|
143
75
|
- config/routes.rb
|
144
76
|
- lib/generators/moirai/install_generator.rb
|
145
77
|
- lib/generators/moirai/migration_generator.rb
|
146
|
-
- lib/generators/moirai/templates/
|
78
|
+
- lib/generators/moirai/templates/create_moirai_translations.rb.erb
|
79
|
+
- lib/generators/moirai/templates/make_moirai_translations_file_path_not_required.rb.erb
|
147
80
|
- lib/i18n/backend/moirai.rb
|
148
81
|
- lib/moirai.rb
|
149
82
|
- lib/moirai/engine.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
Stimulus.register(
|
2
|
-
"moirai-translation",
|
3
|
-
class extends Controller {
|
4
|
-
click(event) {
|
5
|
-
event.preventDefault()
|
6
|
-
}
|
7
|
-
|
8
|
-
submit(event) {
|
9
|
-
const {filePath, key} = event.target.dataset
|
10
|
-
|
11
|
-
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
|
12
|
-
|
13
|
-
const formObject = new FormData()
|
14
|
-
formObject.append('translation[key]', key)
|
15
|
-
formObject.append('translation[file_path]', filePath)
|
16
|
-
formObject.append('translation[value]', event.target.innerText)
|
17
|
-
|
18
|
-
fetch(`/moirai/translation_files`, {
|
19
|
-
method: 'POST',
|
20
|
-
headers: {
|
21
|
-
'X-CSRF-Token': csrfToken
|
22
|
-
},
|
23
|
-
body: formObject
|
24
|
-
})
|
25
|
-
}
|
26
|
-
}
|
27
|
-
);
|
/data/lib/generators/moirai/templates/{migration.rb.erb → create_moirai_translations.rb.erb}
RENAMED
File without changes
|