moirai 0.1.0 → 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 +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
|
+

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