rails_i18n_manager 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +7 -0
- data/README.md +177 -0
- data/Rakefile +18 -0
- data/app/controllers/rails_i18n_manager/application_controller.rb +5 -0
- data/app/controllers/rails_i18n_manager/translation_apps_controller.rb +71 -0
- data/app/controllers/rails_i18n_manager/translations_controller.rb +248 -0
- data/app/helpers/rails_i18n_manager/application_helper.rb +74 -0
- data/app/helpers/rails_i18n_manager/custom_form_builder.rb +223 -0
- data/app/jobs/rails_i18n_manager/application_job.rb +5 -0
- data/app/jobs/rails_i18n_manager/translations_import_job.rb +70 -0
- data/app/lib/rails_i18n_manager/forms/base.rb +25 -0
- data/app/lib/rails_i18n_manager/forms/translation_file_form.rb +55 -0
- data/app/lib/rails_i18n_manager/google_translate.rb +72 -0
- data/app/models/rails_i18n_manager/application_record.rb +20 -0
- data/app/models/rails_i18n_manager/translation_app.rb +108 -0
- data/app/models/rails_i18n_manager/translation_key.rb +161 -0
- data/app/models/rails_i18n_manager/translation_value.rb +11 -0
- data/app/views/layouts/rails_i18n_manager/application.html.slim +55 -0
- data/app/views/layouts/rails_i18n_manager/application.js.erb +5 -0
- data/app/views/rails_i18n_manager/form_builder/_basic_field.html.erb +108 -0
- data/app/views/rails_i18n_manager/form_builder/_error_notification.html.erb +7 -0
- data/app/views/rails_i18n_manager/shared/_flash.html.slim +6 -0
- data/app/views/rails_i18n_manager/translation_apps/_breadcrumbs.html.slim +12 -0
- data/app/views/rails_i18n_manager/translation_apps/_filter_bar.html.slim +9 -0
- data/app/views/rails_i18n_manager/translation_apps/form.html.slim +30 -0
- data/app/views/rails_i18n_manager/translation_apps/index.html.slim +27 -0
- data/app/views/rails_i18n_manager/translations/_breadcrumbs.html.slim +17 -0
- data/app/views/rails_i18n_manager/translations/_filter_bar.html.slim +17 -0
- data/app/views/rails_i18n_manager/translations/_form.html.slim +29 -0
- data/app/views/rails_i18n_manager/translations/_sub_nav.html.slim +7 -0
- data/app/views/rails_i18n_manager/translations/_translation_value_fields.html.slim +10 -0
- data/app/views/rails_i18n_manager/translations/edit.html.slim +6 -0
- data/app/views/rails_i18n_manager/translations/import.html.slim +26 -0
- data/app/views/rails_i18n_manager/translations/index.html.slim +50 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20221001001344_add_rails_i18n_manager_tables.rb +26 -0
- data/lib/rails_i18n_manager/config.rb +20 -0
- data/lib/rails_i18n_manager/engine.rb +42 -0
- data/lib/rails_i18n_manager/version.rb +3 -0
- data/lib/rails_i18n_manager.rb +60 -0
- data/public/rails_i18n_manager/application.css +67 -0
- data/public/rails_i18n_manager/application.js +37 -0
- data/public/rails_i18n_manager/favicon.ico +0 -0
- data/public/rails_i18n_manager/utility.css +99 -0
- metadata +292 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2cb2ef95ffdfb5f8460ccf2ff0b855e308a31d4375c8b90d977c48160323880c
|
4
|
+
data.tar.gz: 78060ff50be1952e29a7113766b4aaccf48b1bf9b9394236b1259c8dd1e43e98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '09dbe770f9e2d99e94c522e313ea3483b2546c0ee9edaa06e907335ade487abe300c931b1415c29f4395917a726c76bdea58089d8e6e296dd154847211b597ce'
|
7
|
+
data.tar.gz: 32d1836c17e69a9a9b9f7cae727415def212fde9d01d6c63dc1cfa9a94551a88654a1629ae15f536067ee0fcbab37c38b5f053863aee7bf72edcc5a5aed81646
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2022 Weston Ganger
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
# Rails I18n Manager
|
2
|
+
> A complete translation editor and workflow
|
3
|
+
|
4
|
+
<a href='https://github.com/westonganger/rails_i18n_manager/actions' target='_blank'><img src="https://github.com/westonganger/rails_i18n_manager/workflows/Tests/badge.svg" style="max-width:100%;" height='21' style='border:0px;height:21px;' border='0' alt="CI Status"></a>
|
5
|
+
|
6
|
+
Web interface to manage i18n translations helping to facilitate the editors of your translations. Provides a low-tech and complete workflow for importing, translating, and exporting your I18n translation files. Designed to allow you to keep the translation files inside your projects git repository where they should be.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
|
10
|
+
- Import & export translations using standard i18n YAML/JSON files
|
11
|
+
- Allows managing translations for any number of apps
|
12
|
+
- Built in support for Google Translation for missing translations
|
13
|
+
- Provides an API end point to perform automated downloads of your translations
|
14
|
+
|
15
|
+
## Screenshots
|
16
|
+
![Screenshot](/screenshot_list.png)
|
17
|
+
<br><br>
|
18
|
+
![Screenshot](/screenshot_import.png)
|
19
|
+
<br><br>
|
20
|
+
![Screenshot](/screenshot_edit.png)
|
21
|
+
|
22
|
+
## Setup
|
23
|
+
|
24
|
+
Developed as a Rails engine. So you can add to any existing app or create a brand new app with the functionality.
|
25
|
+
|
26
|
+
First add the gem to your Gemfile
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
### Gemfile
|
30
|
+
gem "rails_i18n_manager", git: "https://github.com/westonganger/rails_i18n_manager"
|
31
|
+
```
|
32
|
+
|
33
|
+
Then install and run the database migrations
|
34
|
+
|
35
|
+
```sh
|
36
|
+
bundle install
|
37
|
+
bundle exec rake rails_i18n_manager:install:migrations
|
38
|
+
bundle exec rake db:migrate
|
39
|
+
```
|
40
|
+
|
41
|
+
### Routes
|
42
|
+
|
43
|
+
#### Option A: Mount to a path
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
### config/routes.rb
|
47
|
+
|
48
|
+
### As sub-path
|
49
|
+
mount RailsI18nManager::Engine, at: "/rails_i18n_manager", as: "rails_i18n_manager"
|
50
|
+
|
51
|
+
### OR as root-path
|
52
|
+
mount RailsI18nManager::Engine, at: "/", as: "rails_i18n_manager"
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Option B: Mount to a subdomain
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
### config/routes.rb
|
59
|
+
|
60
|
+
translations_engine_subdomain = "translations"
|
61
|
+
|
62
|
+
mount RailsI18nManager::Engine,
|
63
|
+
at: "/", as: "translations_engine",
|
64
|
+
constraints: Proc.new{|request| request.subdomain == translations_engine_subdomain }
|
65
|
+
|
66
|
+
not_engine = Proc.new{|request| request.subdomain != translations_engine_subdomain }
|
67
|
+
|
68
|
+
constraints not_engine do
|
69
|
+
# your app routes here...
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
### Configuration
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
### config/initializers/rails_i18n_manager.rb
|
77
|
+
|
78
|
+
RailsI18nManager.config do |config|
|
79
|
+
config.google_translate_api_key = ENV.fetch("GOOGLE_TRANSLATE_API_KEY", nil)
|
80
|
+
|
81
|
+
### You can use our built-in list of all locales Google Translate supports
|
82
|
+
### OR make your own list. These need to be supported by Google Translate
|
83
|
+
# config.valid_locales = ["en", "es", "fr"]
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
### Customizing Authentication
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
### config/routes.rb
|
91
|
+
|
92
|
+
### Using Devise
|
93
|
+
authenticated :user do
|
94
|
+
mount RailsI18nManager::Engine, at: "/rails_i18n_manager", as: "rails_i18n_manager"
|
95
|
+
end
|
96
|
+
|
97
|
+
### Custom devise-like
|
98
|
+
constraints ->(req){ req.session[:user_id].present? && User.find_by(id: req.session[:user_id]) } do
|
99
|
+
mount RailsI18nManager::Engine, at: "/rails_i18n_manager", as: "rails_i18n_manager"
|
100
|
+
end
|
101
|
+
|
102
|
+
### HTTP Basic Auth
|
103
|
+
with_http_basic_auth = ->(engine){
|
104
|
+
Rack::Builder.new do
|
105
|
+
use Rack::Auth::Basic do |username, password|
|
106
|
+
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username), ::Digest::SHA256.hexdigest(ENV.fetch("RAILS_I18N_MANAGER_USERNAME"))) &&
|
107
|
+
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV.fetch("RAILS_I18N_MANAGER_PASSWORD")))
|
108
|
+
end
|
109
|
+
run(engine)
|
110
|
+
end
|
111
|
+
}
|
112
|
+
mount with_http_basic_auth.call(RailsI18nManager::Engine), at: "/rails_i18n_manager", as: "rails_i18n_manager"
|
113
|
+
```
|
114
|
+
|
115
|
+
### API Endpoint
|
116
|
+
|
117
|
+
We provide an endpoint to retrieve your translation files at `/translations`.
|
118
|
+
|
119
|
+
You will likely want to add your own custom authentication strategy and can do so using a routing constraint on the `mount RailsI18nManager` call.
|
120
|
+
|
121
|
+
From that point you can implement an automated mechanism to update your apps translations using the provided API end point. Some examples
|
122
|
+
|
123
|
+
An example in Ruby:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
require 'open-uri'
|
127
|
+
|
128
|
+
zip_stream = URI.open('https://translations-manager.example.com/translations.zip?export_format=yaml')
|
129
|
+
IO.copy_stream(zip_stream, '/tmp/my-app-locales.zip')
|
130
|
+
`unzip /tmp/my-app-locales.zip /tmp/my-app-locales/`
|
131
|
+
`rsync --delete-after /tmp/my-app-locales/my-app/ /path/to/my-app/config/locales/`
|
132
|
+
puts "Locales are now updated, app restart not-required"
|
133
|
+
```
|
134
|
+
|
135
|
+
A command line example using curl:
|
136
|
+
|
137
|
+
```
|
138
|
+
curl https://translations-manager.example.com/translations.zip?export_format=json -o /tmp/my-app-locales.zip \
|
139
|
+
&& unzip /tmp/my-app-locales.zip /tmp/my-app-locales/ \
|
140
|
+
&& rsync --delete-after /tmp/my-app-locales/my-app/ \
|
141
|
+
&& echo "Locales are now updated, app restart not-required"
|
142
|
+
```
|
143
|
+
|
144
|
+
## Development
|
145
|
+
|
146
|
+
Run migrations using: `rails db:migrate`
|
147
|
+
|
148
|
+
Run server using: `bin/dev` or `cd test/dummy/; rails s`
|
149
|
+
|
150
|
+
## Testing
|
151
|
+
|
152
|
+
```
|
153
|
+
bundle exec rspec
|
154
|
+
```
|
155
|
+
|
156
|
+
We can locally test different versions of Rails using `ENV['RAILS_VERSION']` and different database gems using `ENV['DB_GEM']`
|
157
|
+
|
158
|
+
```
|
159
|
+
export RAILS_VERSION=7.0
|
160
|
+
export DB_GEM=sqlite3
|
161
|
+
bundle install
|
162
|
+
bundle exec rspec
|
163
|
+
```
|
164
|
+
|
165
|
+
## Other Translation Managers & Web Interfaces
|
166
|
+
|
167
|
+
For comparison, some other projects for managing Rails translations.
|
168
|
+
|
169
|
+
- https://github.com/tolk/tolk - This is the project that inspired rails_i18n_manager. UI and file-based approach. I [attempted to revive tolk](https://github.com/tolk/tolk/pull/161) but gave up as I found the codebase and workflow was really just a legacy ball of spagetti.
|
170
|
+
- https://github.com/prograils/lit
|
171
|
+
- https://github.com/alphagov/rails_translation_manager
|
172
|
+
- https://github.com/glebm/i18n-tasks
|
173
|
+
|
174
|
+
|
175
|
+
# Credits
|
176
|
+
|
177
|
+
Created & Maintained by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
8
|
+
load 'rails/tasks/engine.rake'
|
9
|
+
|
10
|
+
load 'rails/tasks/statistics.rake'
|
11
|
+
|
12
|
+
require 'bundler/gem_tasks'
|
13
|
+
|
14
|
+
task :test do
|
15
|
+
system("rspec", out: STDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class TranslationAppsController < ApplicationController
|
3
|
+
before_action :set_translation_app, only: [:show, :edit, :update, :destroy]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@translation_apps = TranslationApp
|
7
|
+
.sort_order(params[:sort], params[:direction], base_sort_order: "#{TranslationApp.table_name}.name ASC")
|
8
|
+
.multi_search(params[:search])
|
9
|
+
.page(params[:page])
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
@translation_app = TranslationApp.new
|
14
|
+
render "form"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
@translation_app = TranslationApp.new(permitted_params)
|
19
|
+
|
20
|
+
if @translation_app.save
|
21
|
+
flash[:notice] = "Successfully created."
|
22
|
+
redirect_to action: :edit, id: @translation_app.id
|
23
|
+
else
|
24
|
+
flash.now[:error] = "Create failed."
|
25
|
+
render "rails_i18n_manager/translation_apps/form"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def show
|
30
|
+
redirect_to action: :edit
|
31
|
+
end
|
32
|
+
|
33
|
+
def edit
|
34
|
+
render "form"
|
35
|
+
end
|
36
|
+
|
37
|
+
def update
|
38
|
+
if @translation_app.update(permitted_params)
|
39
|
+
flash[:notice] = "Update success."
|
40
|
+
redirect_to action: :index
|
41
|
+
else
|
42
|
+
flash.now[:error] = "Update failed."
|
43
|
+
render "rails_i18n_manager/translation_apps/form"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroy
|
48
|
+
if @translation_app.destroy
|
49
|
+
flash[:notice] = "Deleted '#{@translation_app.name}'"
|
50
|
+
else
|
51
|
+
flash[:alert] = "Delete failed"
|
52
|
+
end
|
53
|
+
redirect_to action: :index
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def set_translation_app
|
59
|
+
@translation_app = TranslationApp.find_by!(id: params[:id])
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_browser_title
|
63
|
+
@browser_title = TranslationApp::NAME.pluralize
|
64
|
+
end
|
65
|
+
|
66
|
+
def permitted_params
|
67
|
+
params.require(:translation_app).permit(:name, :default_locale, additional_locales: [])
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class TranslationsController < ApplicationController
|
3
|
+
before_action :get_translation_key, only: [:show, :edit, :update, :destroy]
|
4
|
+
|
5
|
+
def index
|
6
|
+
case params[:sort]
|
7
|
+
when "app_name"
|
8
|
+
sort = "#{TranslationApp.table_name}.name"
|
9
|
+
when "updated_at"
|
10
|
+
sort = "#{TranslationKey.table_name}.updated_at"
|
11
|
+
else
|
12
|
+
sort = params[:sort]
|
13
|
+
end
|
14
|
+
|
15
|
+
@translation_keys = TranslationKey
|
16
|
+
.includes(:translation_app, :translation_values)
|
17
|
+
.references(:translation_app)
|
18
|
+
.sort_order(sort, params[:direction], base_sort_order: "#{TranslationApp.table_name}.name ASC, #{TranslationKey.table_name}.key ASC")
|
19
|
+
|
20
|
+
apply_filters
|
21
|
+
|
22
|
+
if request.format.to_sym != :html && TranslationApp.first.nil?
|
23
|
+
request.format = :html
|
24
|
+
flash[:alert] = "No Translation apps exists"
|
25
|
+
redirect_to action: :index
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
respond_to do |format|
|
30
|
+
format.html do
|
31
|
+
@translation_keys = @translation_keys.page(params[:page])
|
32
|
+
end
|
33
|
+
|
34
|
+
format.any do
|
35
|
+
@translations_keys = @translation_keys.where(active: true) ### Ensure exported keys are active for any exports
|
36
|
+
|
37
|
+
case request.format.to_sym
|
38
|
+
when :csv
|
39
|
+
send_data @translation_keys.to_csv, filename: "translations.csv"
|
40
|
+
when :zip
|
41
|
+
file = @translation_keys.export_to(format: params[:export_format], zip: true, app_name: params[:app_name].presence)
|
42
|
+
|
43
|
+
if file
|
44
|
+
send_file file, filename: "translations-#{params[:export_format]}-#{params[:app_name].presence || "all-apps"}.zip"
|
45
|
+
else
|
46
|
+
flash[:alert] = "Sorry, Nothing to export"
|
47
|
+
redirect_to action: :index
|
48
|
+
end
|
49
|
+
else
|
50
|
+
raise ActionController::UnknownFormat
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def show
|
57
|
+
render "edit"
|
58
|
+
end
|
59
|
+
|
60
|
+
def edit
|
61
|
+
end
|
62
|
+
|
63
|
+
def update
|
64
|
+
@translation_key.assign_attributes(allowed_params)
|
65
|
+
|
66
|
+
if @translation_key.save
|
67
|
+
flash[:notice] = "Update success."
|
68
|
+
redirect_to edit_translation_path(@translation_key)
|
69
|
+
else
|
70
|
+
flash[:notice] = "Update failed."
|
71
|
+
render "translations/edit"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def destroy
|
76
|
+
if @translation_key.active
|
77
|
+
redirect_to translations_path, alert: "Cannot delete active translations"
|
78
|
+
else
|
79
|
+
@translation_key.destroy!
|
80
|
+
redirect_to translations_path, notice: "Delete Successful"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_inactive_keys
|
85
|
+
@translation_keys = TranslationKey
|
86
|
+
.where(active: false)
|
87
|
+
.includes(:translation_app, :translation_values)
|
88
|
+
.references(:translation_app)
|
89
|
+
|
90
|
+
apply_filters
|
91
|
+
|
92
|
+
ids = translation_keys.pluck(:id)
|
93
|
+
|
94
|
+
TranslationKey.where(id: ids).delete_all
|
95
|
+
TranslationValue.where(translation_key_id: ids).delete_all
|
96
|
+
end
|
97
|
+
|
98
|
+
def import
|
99
|
+
@form = Forms::TranslationFileForm.new(params[:import_form])
|
100
|
+
|
101
|
+
if request.get?
|
102
|
+
render
|
103
|
+
else
|
104
|
+
if @form.valid?
|
105
|
+
begin
|
106
|
+
TranslationsImportJob.new.perform(
|
107
|
+
translation_app_id: @form.translation_app_id,
|
108
|
+
import_file: @form.file.path,
|
109
|
+
overwrite_existing: @form.overwrite_existing,
|
110
|
+
mark_inactive_translations: @form.mark_inactive_translations,
|
111
|
+
)
|
112
|
+
rescue TranslationsImportJob::ImportAbortedError => e
|
113
|
+
flash.now.alert = e.message
|
114
|
+
render
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
redirect_to translations_path, notice: "Import Successful"
|
119
|
+
else
|
120
|
+
flash.now.alert = "Import not started due to form errors."
|
121
|
+
render
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def translate_missing
|
127
|
+
@translation_keys = TranslationKey.includes(:translation_values)
|
128
|
+
|
129
|
+
apply_filters
|
130
|
+
|
131
|
+
translated_count = 0
|
132
|
+
total_missing = 0
|
133
|
+
|
134
|
+
if params[:app_name]
|
135
|
+
app_locales = TranslationApp.find_by(name: params[:app_name]).additional_locales_array
|
136
|
+
else
|
137
|
+
@translation_keys = @translation_keys.includes(:translation_app)
|
138
|
+
end
|
139
|
+
|
140
|
+
### Check & Translate for Every i18n key
|
141
|
+
@translation_keys.each do |key_record|
|
142
|
+
locales = (app_locales || key_record.translation_app.additional_locales_array)
|
143
|
+
|
144
|
+
### Filter to just google translate supported languages
|
145
|
+
locales = locales.intersection(GoogleTranslate.supported_locales)
|
146
|
+
|
147
|
+
default_translation_text = key_record.default_translation
|
148
|
+
|
149
|
+
next if default_translation_text.blank?
|
150
|
+
|
151
|
+
locales.each do |locale|
|
152
|
+
if locale == key_record.translation_app.default_locale
|
153
|
+
next ### skip, we dont translate the default locale
|
154
|
+
end
|
155
|
+
|
156
|
+
val_record = key_record.translation_values.detect{|x| x.locale == locale.to_s }
|
157
|
+
|
158
|
+
### Translate Missing
|
159
|
+
if val_record.nil? || val_record.translation.blank?
|
160
|
+
total_missing += 1
|
161
|
+
|
162
|
+
translated_text = GoogleTranslate.translate(
|
163
|
+
default_translation_text,
|
164
|
+
from: key_record.translation_app.default_locale,
|
165
|
+
to: locale
|
166
|
+
)
|
167
|
+
|
168
|
+
if translated_text.present?
|
169
|
+
if val_record.nil?
|
170
|
+
val_record = key_record.translation_values.new(locale: locale)
|
171
|
+
end
|
172
|
+
|
173
|
+
val_record.assign_attributes(translation: translated_text)
|
174
|
+
|
175
|
+
val_record.save!
|
176
|
+
|
177
|
+
translated_count += 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if params[:translation_key_id]
|
184
|
+
url = request.referrer || translation_path(params[:translation_key_id])
|
185
|
+
else
|
186
|
+
url = params.to_unsafe_h.merge(action: :index)
|
187
|
+
end
|
188
|
+
|
189
|
+
redirect_to url, notice: "Translated #{translated_count} of #{total_missing} total missing translations"
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def get_translation_key
|
195
|
+
@translation_key = TranslationKey.includes(:translation_values).find_by!(id: params[:id])
|
196
|
+
end
|
197
|
+
|
198
|
+
def allowed_params
|
199
|
+
params.require(:translation_key).permit(translation_values_attributes: [:id, :locale, :translation])
|
200
|
+
end
|
201
|
+
|
202
|
+
def apply_filters
|
203
|
+
if params[:app_name].present?
|
204
|
+
@translation_keys = @translation_keys.joins(:translation_app).where(TranslationApp.table_name => {name: params[:app_name]})
|
205
|
+
end
|
206
|
+
|
207
|
+
if params[:translation_key_id].present?
|
208
|
+
@translation_keys = @translation_keys.where(id: params[:translation_key_id])
|
209
|
+
end
|
210
|
+
|
211
|
+
if request.format.html?
|
212
|
+
### ONLY FOR HTML - SO THAT WE DONT DOWNLOAD INCOMPLETE TRANSLATION EXPORT PACKAGES
|
213
|
+
|
214
|
+
if params[:search].present?
|
215
|
+
@translation_keys = @translation_keys.search(params[:search])
|
216
|
+
end
|
217
|
+
|
218
|
+
if params[:status] == "Inactive"
|
219
|
+
@translation_keys = @translation_keys.where(active: false)
|
220
|
+
elsif params[:status] == "All"
|
221
|
+
# Do nothing
|
222
|
+
else
|
223
|
+
@translation_keys = @translation_keys.where(active: true)
|
224
|
+
end
|
225
|
+
|
226
|
+
if params[:status] == "Missing"
|
227
|
+
missing_key_ids = []
|
228
|
+
TranslationApp.all.each do |app_record|
|
229
|
+
app_record.translation_keys.includes(:translation_values).each do |key_record|
|
230
|
+
if key_record.translation_values.size != app_record.all_locales.size
|
231
|
+
missing_key_ids << key_record.id
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@translation_keys = @translation_keys
|
237
|
+
.references(:translation_values)
|
238
|
+
.where("#{TranslationValue.table_name}.translation IS NULL OR #{TranslationKey.table_name}.id IN (:ids)", ids: missing_key_ids)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def set_browser_title
|
244
|
+
@browser_title = "Translations"
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
module ApplicationHelper
|
3
|
+
|
4
|
+
def custom_form_for(*args, **options, &block)
|
5
|
+
options[:builder] = CustomFormBuilder
|
6
|
+
if options.has_key?(:defaults)
|
7
|
+
@_custom_form_for_defaults = options.delete(:defaults)
|
8
|
+
end
|
9
|
+
form_for(*args, options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def custom_fields_for(*args, **options, &block)
|
13
|
+
options[:builder] = CustomFormBuilder
|
14
|
+
if options.has_key?(:defaults)
|
15
|
+
@_custom_form_for_defaults = options.delete(:defaults)
|
16
|
+
end
|
17
|
+
fields_for(*args, options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
ASSET_VERSION = `git show -s --format=%ci`.parameterize.freeze
|
21
|
+
def custom_asset_path(path)
|
22
|
+
"#{path}?v=#{Rails.env.development? ? Time.now.to_i : ASSET_VERSION}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def breadcrumb_item(title, url=nil)
|
26
|
+
if url.nil?
|
27
|
+
%Q(<span class="breadcrumb-item">#{title}</span>).html_safe
|
28
|
+
else
|
29
|
+
%Q(<span class="breadcrumb-item"><a href="#{url}">#{title}</a></span>).html_safe
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def sort_link(attr_name, label=nil)
|
34
|
+
if label.blank?
|
35
|
+
label = attr_name.to_s.titleize
|
36
|
+
end
|
37
|
+
|
38
|
+
direction = params[:direction].present? && params[:direction].casecmp?("asc") ? 'desc' : 'asc'
|
39
|
+
|
40
|
+
link_to label, params.to_unsafe_h.merge(sort: attr_name, direction: direction)
|
41
|
+
end
|
42
|
+
|
43
|
+
def nav_link(name, url, html_options={}, &block)
|
44
|
+
url = url_for(url)
|
45
|
+
|
46
|
+
if html_options.has_key?(:active)
|
47
|
+
active = html_options.delete(:active)
|
48
|
+
elsif url == (url.include?("?") ? request.fullpath : request.path)
|
49
|
+
active = true
|
50
|
+
end
|
51
|
+
|
52
|
+
html_options[:class] ||= ""
|
53
|
+
|
54
|
+
html_options[:class] += " nav-link"
|
55
|
+
|
56
|
+
if active
|
57
|
+
html_options[:class] += " active"
|
58
|
+
end
|
59
|
+
|
60
|
+
html_options[:class].strip!
|
61
|
+
|
62
|
+
content_tag(:li, class: "nav-item #{'active' if active}".strip) do
|
63
|
+
link_to(name, url, html_options) + (
|
64
|
+
if block_given?
|
65
|
+
content_tag(:ul) do
|
66
|
+
capture(&block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|