rails_i18n_manager 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: defa73fd31070325ba7285823e0a03ace7ab8c9a5808030f26638c0019de1acd
4
- data.tar.gz: 11ea4841b06c54736b5be651d5d8e50d7bb232e4aedbfeaa82f7c9883bf7b4e7
3
+ metadata.gz: 754d64695feb342f539cf20c2f7e0eb56307d347c09a0aff1ee2dd6424bef236
4
+ data.tar.gz: dc20afbd566dd69f283f8e520fa523d551ece87e00bc8d2dc7361f7043890a1f
5
5
  SHA512:
6
- metadata.gz: 95c8a9c7e660fbe4b3294eca65aceda6a3dd0c550acb41cf7abdb1aa4c585e45c529cee2b81d18ac692039d5525c07ae6b80c8a242963ff388cbd12f13db9f20
7
- data.tar.gz: 79fd6614787767d5173d5b5812515dc7118f4dcfe2d9583a160c61b8760b13f7b06e8baaa5cb4a09c9ab1a60213d933d87c753db7f68881ef4ac71435d3299b0
6
+ metadata.gz: 9c7271c5b764f6730d8fdb33e31e936b9ceb738391d170f663ec133543eddaf52843b24bd20ebf97c671d507e1193dcae0671c25d6bdfcca27a9f2d737f040f0
7
+ data.tar.gz: d1c51faed0c9049ca72c9c6891a531338fcdcc183d08dc65bf3de3d558328bb1cdddfca96b99eef5500ac1b409d66c26563b0fa1ff8a069e770ed04722815538
data/README.md CHANGED
@@ -142,6 +142,88 @@ curl https://translations-manager.example.com/translations.zip?export_format=jso
142
142
  && echo "Locales are now updated, app restart not-required"
143
143
  ```
144
144
 
145
+ ## Recommended Workflow for Teams
146
+
147
+ It is desirable to reduce how often import/export is performed. It is also desirable that we do not violate the regular PR lifecycle/process. The following workflow should allow for this.
148
+
149
+ When creating a PR you can just create a new YAML file named after your feature name or ticket number and then use the following format:
150
+
151
+ ```yaml
152
+ # config/locales/some_new_feature.yml
153
+
154
+ en:
155
+ some_new_key: "foo"
156
+ fr:
157
+ some_new_key: "bar"
158
+ es:
159
+ some_new_key: "baz"
160
+ ```
161
+
162
+ Whenever releasing a new version of your application, pre-deploy or some other cadence, then you can have a step where all translation files are uploaded to the `rails_i18n_manager`, have your translator folks double check everything, then export your new files and cleanup all the feature files.
163
+
164
+ ## Recommended I18n Configuration
165
+
166
+ The default I18n backend has some glaring issues
167
+
168
+ - It will silently show "translation_missing" text which is very undesirable
169
+ - It will not fallback to your default or any other locale
170
+
171
+ You can avoid these issues using either of the techniques below
172
+
173
+ ```ruby
174
+ # config/initializers/i18n.rb
175
+
176
+ Rails.configuration do |config|
177
+ config.i18n.raise_on_missing_translations = true # WARNING: this will raise exceptions in Production too, preventing your users from using your application even when some silly little translation is missing
178
+
179
+ config.i18n.fallbacks = [I18n.default_locale, :en].uniq # fallback to default locale, or if that is missing then fallback to english translation
180
+ end
181
+ ```
182
+
183
+ You will likely find that `raise_on_missing_translations` is too aggressive. Causing major outages just because a translation is missing. In that scenario its better to use something like the following:
184
+
185
+ ```ruby
186
+ # config/initializers/i18n.rb
187
+
188
+ Rails.configuration do |config|
189
+ config.i18n.raise_on_missing_translations = false # Instead we use the custom backend below
190
+
191
+ config.i18n.fallbacks = [I18n.default_locale, :en].uniq # fallback to default locale, or if that is missing then fallback to english translation
192
+ end
193
+
194
+ module I18n
195
+ class CustomI18nBackend
196
+ include I18n::Backend::Base
197
+
198
+ def translate(locale, key, options = EMPTY_HASH)
199
+ if !key.nil? && key.to_s != "i18n.plural.rule"
200
+ translation_value = lookup(locale, key, options[:scope], options)
201
+
202
+ if translation_value.blank?
203
+ if Rails.env.production?
204
+ # send an email or some other warning mechanism
205
+ else
206
+ # Raise exception in non-production environments
207
+ raise "Translation not found (locale: #{locale}, key: #{key})"
208
+ end
209
+ end
210
+ end
211
+
212
+ return nil # allow the Backend::Chain to continue to the next backend
213
+ end
214
+ end
215
+ end
216
+
217
+ if I18n.backend.is_a?(I18n::Backend::Chain)
218
+ I18n.backend.backends.unshift(I18n::CustomI18nBackend)
219
+ else
220
+ I18n.backend = I18n::Backend::Chain.new(
221
+ I18n::CustomI18nBackend,
222
+ I18n.backend, # retain original backend
223
+ )
224
+ end
225
+ ```
226
+
145
227
  ## Development
146
228
 
147
229
  Run migrations using: `rails db:migrate`
@@ -40,7 +40,7 @@ module RailsI18nManager
40
40
  redirect_to action: :index
41
41
  else
42
42
  flash.now[:error] = "Update failed."
43
- render "rails_i18n_manager/translation_apps/form"
43
+ render "form"
44
44
  end
45
45
  end
46
46
 
@@ -36,12 +36,16 @@ module RailsI18nManager
36
36
 
37
37
  case request.format.to_sym
38
38
  when :csv
39
+ if params.dig(:filters, :app_name).present?
40
+ @translation_keys = @translation_keys.joins(:translation_app).where(TranslationApp.table_name => {name: params.dig(:filters, :app_name)})
41
+ end
42
+
39
43
  send_data @translation_keys.to_csv, filename: "translations.csv"
40
44
  when :zip
41
- file = @translation_keys.export_to(format: params[:export_format], zip: true, app_name: params[:app_name].presence)
45
+ file = @translation_keys.export_to(format: params[:export_format], zip: true, app_name: params.dig(:filters, :app_name).presence)
42
46
 
43
47
  if file
44
- send_file file, filename: "translations-#{params[:export_format]}-#{params[:app_name].presence || "all-apps"}.zip"
48
+ send_file file, filename: "translations-#{params[:export_format]}-#{params.dig(:filters, :app_name).presence || "all-apps"}.zip"
45
49
  else
46
50
  flash[:alert] = "Sorry, Nothing to export"
47
51
  redirect_to action: :index
@@ -68,7 +72,7 @@ module RailsI18nManager
68
72
  redirect_to edit_translation_path(@translation_key)
69
73
  else
70
74
  flash[:notice] = "Update failed."
71
- render "translations/edit"
75
+ render "edit"
72
76
  end
73
77
  end
74
78
 
@@ -89,10 +93,10 @@ module RailsI18nManager
89
93
 
90
94
  apply_filters
91
95
 
92
- ids = translation_keys.pluck(:id)
96
+ @translation_keys.destroy_all
93
97
 
94
- TranslationKey.where(id: ids).delete_all
95
- TranslationValue.where(translation_key_id: ids).delete_all
98
+ flash[:notice] = "Delete Inactive was successful."
99
+ redirect_to translations_path(filters: params[:filters].to_unsafe_h)
96
100
  end
97
101
 
98
102
  def import
@@ -132,8 +136,8 @@ module RailsI18nManager
132
136
  translated_count = 0
133
137
  total_missing = 0
134
138
 
135
- if params[:app_name]
136
- app_locales = TranslationApp.find_by(name: params[:app_name]).additional_locales_array
139
+ if params.dig(:filters, :app_name)
140
+ app_locales = TranslationApp.find_by(name: params.dig(:filters, :app_name)).additional_locales_array
137
141
  else
138
142
  @translation_keys = @translation_keys.includes(:translation_app)
139
143
  end
@@ -184,7 +188,7 @@ module RailsI18nManager
184
188
  if params[:translation_key_id]
185
189
  url = request.referrer || translation_path(params[:translation_key_id])
186
190
  else
187
- url = params.to_unsafe_h.merge(action: :index)
191
+ url = translations_path(filters: params[:filters].to_unsafe_h)
188
192
  end
189
193
 
190
194
  redirect_to url, notice: "Translated #{translated_count} of #{total_missing} total missing translations"
@@ -201,8 +205,8 @@ module RailsI18nManager
201
205
  end
202
206
 
203
207
  def apply_filters
204
- if params[:app_name].present?
205
- @translation_keys = @translation_keys.joins(:translation_app).where(TranslationApp.table_name => {name: params[:app_name]})
208
+ if params.dig(:filters, :app_name).present?
209
+ @translation_keys = @translation_keys.joins(:translation_app).where(TranslationApp.table_name => {name: params.dig(:filters, :app_name)})
206
210
  end
207
211
 
208
212
  if params[:translation_key_id].present?
@@ -212,24 +216,40 @@ module RailsI18nManager
212
216
  if request.format.html?
213
217
  ### ONLY FOR HTML - SO THAT WE DONT DOWNLOAD INCOMPLETE TRANSLATION EXPORT PACKAGES
214
218
 
215
- if params[:search].present?
216
- @translation_keys = @translation_keys.search(params[:search])
219
+ if params.dig(:filters, :search).present?
220
+ @translation_keys = @translation_keys.search(params.dig(:filters, :search))
221
+ end
222
+
223
+ if params.dig(:filters, :status).blank?
224
+ params[:filters] ||= {}
225
+ params[:filters][:status] = "All Active Translations"
217
226
  end
218
227
 
219
- if params[:status] == "Inactive"
228
+ if params.dig(:filters, :status) == "Inactive Translations"
220
229
  @translation_keys = @translation_keys.where(active: false)
221
- elsif params[:status] == "All"
230
+ elsif params.dig(:filters, :status) == "All Translations"
222
231
  # Do nothing
223
- else
232
+ elsif params.dig(:filters, :status) == "All Active Translations"
224
233
  @translation_keys = @translation_keys.where(active: true)
225
- end
226
-
227
- if params[:status] == "Missing"
234
+ elsif params.dig(:filters, :status).start_with?("Missing")
228
235
  missing_key_ids = []
229
- TranslationApp.all.each do |app_record|
230
- app_record.translation_keys.includes(:translation_values).each do |key_record|
231
- if key_record.translation_values.size != app_record.all_locales.size
232
- missing_key_ids << key_record.id
236
+
237
+ if params.dig(:filters, :app_name).present?
238
+ translation_apps = TranslationApp.where(name: params.dig(:filters, :app_name))
239
+ else
240
+ translation_apps = TranslationApp.all
241
+ end
242
+
243
+ translation_apps.includes(translation_keys: [:translation_values]).each do |app_record|
244
+ app_record.translation_keys.each do |key_record|
245
+ if params.dig(:filters, :status) == "Missing Default Translation"
246
+ if key_record.translation_values.detect{|x| x.locale == app_record.default_locale}&.translation.blank?
247
+ missing_key_ids << key_record.id
248
+ end
249
+ else
250
+ if key_record.any_missing_translations?
251
+ missing_key_ids << key_record.id
252
+ end
233
253
  end
234
254
  end
235
255
  end
@@ -1,4 +1,10 @@
1
1
  <style>
2
+ .pull-left{
3
+ float: left !important;
4
+ }
5
+ .pull-right{
6
+ float: right !important;
7
+ }
2
8
  .space-left{
3
9
  margin-left:5px !important;
4
10
  }
@@ -1,17 +1,18 @@
1
1
  form
2
2
  .row.align-items-center.g-1
3
3
  .col-auto
4
- = select_tag :app_name, options_for_select(RailsI18nManager::TranslationApp.order(name: :asc).pluck(:name), params[:app_name]), prompt: "All Apps", class: "form-select", style: "min-width: 220px"
4
+ = select_tag "filters[app_name]", options_for_select(RailsI18nManager::TranslationApp.order(name: :asc).pluck(:name), params.dig(:filters, :app_name)), prompt: "All Apps", class: "form-select", style: "min-width: 220px"
5
5
 
6
6
  .col-auto
7
- = select_tag :status, options_for_select([["All Active", nil], "Missing", "Inactive", "All"], params[:status]), class: 'form-select', style: "min-width: 135px;"
7
+ = select_tag :status, options_for_select(["All Translations", "All Active Translations", "Inactive Translations", "Missing Default Translation", "Missing Any Translation"], params.dig(:filters, :status)), class: 'form-select', style: "min-width: 215px;"
8
8
 
9
9
  .col-auto
10
- = text_field_tag :search, params[:search], placeholder: "Search", class: "form-control"
10
+ = text_field_tag :search, params.dig(:filters, :search), placeholder: "Search", class: "form-control"
11
11
 
12
12
  .col-auto
13
13
  button.btn.btn-primary.btn-sm type="submit" Filter
14
14
 
15
- - if [:app_name, :search].any?{|x| params[x].present? }
16
- - link_params = {status: params[:status]}.select{|_,v| v.present?}
17
- = link_to "Clear", link_params, class: "btn btn-sm space-left"
15
+ - if params.dig(:filters, :app_name).present? || params.dig(:filters, :search).present?
16
+ - if params.dig(:filters, :status).present?
17
+ - link_params = {filters: {status: params.dig(:filters, :status)}}
18
+ = link_to "Clear", (link_params || {}), class: "btn btn-sm space-left"
@@ -1,19 +1,19 @@
1
1
  .pull-right
2
- = link_to "Translate with Google", params.to_unsafe_h.merge(action_name: :translate_missing), class: "btn btn-primary btn-sm", "data-confirm" => "Are you sure you want to proceed with translating the missing translations in the currently filtered list?"
2
+ = link_to "Translate with Google", translate_missing_translations_path(filters: params[:filters].to_unsafe_h), class: "btn btn-primary btn-sm", "data-method" => "post", "data-confirm" => "Are you sure you want to proceed with translating the missing translations in the currently filtered list?"
3
3
  = link_to "Import Translations", import_translations_path, class: "btn btn-secondary btn-sm space-left2"
4
- = link_to "Delete Inactive", params.to_unsafe_h.merge(action_name: :delete_inactive_keys), class: "btn btn-danger btn-sm space-left2", data: {confirm: "Warning! This is a highly destructive action.\n\nIts possible to incorrectly upload an incomplete or incorrect file to 'Mark Inactive Translations from Source' which can leave you with inactive keys that maybe shouldnt have been inactivated.\n\nPlease proceed only if you are certain that you do not have any keys that are incorrectly marked inactive.\n\nAre you sure you want to proceed with deleting the inactive translations in the currently filtered list?"}
4
+ = link_to "Delete Inactive", delete_inactive_keys_translations_path(filters: params[:filters].to_unsafe_h), class: "btn btn-danger btn-sm space-left2", "data-method" => "delete", "data-confirm" => "Warning! This is a highly destructive action.\n\nIts possible to incorrectly upload an incomplete or incorrect file to 'Mark Inactive Translations from Source' which can leave you with inactive keys that maybe shouldnt have been inactivated.\n\nPlease proceed only if you are certain that you do not have any keys that are incorrectly marked inactive.\n\nAre you sure you want to proceed with deleting the inactive translations in the currently filtered list?"
5
5
 
6
6
  h2.page-title Translations
7
- - if params[:app_name]
7
+ - if params[:app_name].present?
8
8
  h5.page-title App Name: #{params[:app_name]}
9
9
 
10
10
  br
11
11
 
12
12
  .well.well-sm
13
13
  .btn-group.pull-right.text-right
14
- = link_to "Export to CSV", params.to_unsafe_h.merge(format: :csv), class: "btn btn-sm btn-success"
15
- = link_to "YAML", params.to_unsafe_h.merge(format: :zip, export_format: :yaml), class: "btn btn-sm btn-success"
16
- = link_to "JSON", params.to_unsafe_h.merge(format: :zip, export_format: :json), class: "btn btn-sm btn-success"
14
+ = link_to "Export to CSV", translations_path(format: :csv, app_name: params[:app_name]), class: "btn btn-sm btn-success"
15
+ = link_to "YAML", translations_path(format: :zip, export_format: :yaml, app_name: params[:app_name]), class: "btn btn-sm btn-success"
16
+ = link_to "JSON", translations_path(format: :zip, export_format: :json, app_name: params[:app_name]), class: "btn btn-sm btn-success"
17
17
 
18
18
  .pull-right.space-right2
19
19
 
@@ -25,7 +25,7 @@ table.table.table-striped.table-hover.space-above3.list-table
25
25
  th = sort_link(:app_name)
26
26
  th = sort_link(:key)
27
27
  th Default Translation
28
- - if params[:status] == "Inactive"
28
+ - if params[:status] != "All Active Translations"
29
29
  th Status
30
30
  th = sort_link(:updated_at)
31
31
  th Actions
@@ -35,7 +35,7 @@ table.table.table-striped.table-hover.space-above3.list-table
35
35
  td = x.translation_app.name
36
36
  td = x.key
37
37
  td = x.default_translation
38
- - if params[:status] == "Inactive"
38
+ - if params[:status] != "All Active Translations"
39
39
  td Inactive
40
40
  td = x.updated_at&.strftime("%Y-%m-%d %l:%M %p")
41
41
  td
@@ -1,3 +1,3 @@
1
1
  module RailsI18nManager
2
- VERSION = "1.1.3".freeze
2
+ VERSION = "1.1.4".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_i18n_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Weston Ganger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-09 00:00:00.000000000 Z
11
+ date: 2025-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails