i18n-active_record 0.4.0 → 1.1.0

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: 7aeed8d25f893fefde754e0836bdc6338529b6313a5809d4312fb7fd30087f3a
4
- data.tar.gz: e924f6936a5f956f6c62c095374b76d1c84e504d65bdf07f4463d2659cb7c50d
3
+ metadata.gz: 934e01acf036e3a1c8eb23f5f8f96c55e84d462a31a10183c12389e8466e233d
4
+ data.tar.gz: a0c8301e9e0c5eaac6badf2dfdeca19b581296eaa0e0a15464a13fbc6796087f
5
5
  SHA512:
6
- metadata.gz: 100bd6c09aa1307292e2f751d45b601e47837e58d708bf881b9e0ba6d02e1ec1d8911d5fa4c3d4f0d6d9409e5997d90772e23e1fdbf86e0e4ed6b1af48101988
7
- data.tar.gz: 29bd66389ac95bf5afa7f97f89e9676b6bb83be6abaa02876e7577dff6c1a83b7f91fbe70f6166841e5924b403e9449c071338076ef8f3c5d1189bd8f3e9f29a
6
+ metadata.gz: 0702e26d0b3a600ff2d28db7473e9a34311f18a099a0009ec6bccec5382d5c4bcc9042b33653a214c377c332db0d902adb64103e05a98c1371d6d19ccb3c0297
7
+ data.tar.gz: f2dd449433f4b88763c5952831feff25956c727aa4ff086c2ae130709bfd968ef36f61bf7b2af9065681ac7132e7e7c3a05c13b268e5c1c3e282285cd1787c6b
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # I18n::Backend::ActiveRecord [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop) [![Tests Status](https://github.com/svenfuchs/i18n-active_record/actions/workflows/test.yml/badge.svg)](https://github.com/svenfuchs/i18n-active_record/actions) [![Linter Status](https://github.com/svenfuchs/i18n-active_record/actions/workflows/linter.yml/badge.svg)](https://github.com/svenfuchs/i18n-active_record/actions)
2
+
3
+ This repository contains the I18n ActiveRecord backend and support code that has been extracted from the `I18n` gem: http://github.com/svenfuchs/i18n.
4
+ It is fully compatible with Rails 4, 5 and 6.
5
+
6
+ ## Installation
7
+
8
+ For Bundler put the following in your Gemfile:
9
+
10
+ ```ruby
11
+ gem 'i18n-active_record', require: 'i18n/active_record'
12
+ ```
13
+
14
+ After updating your bundle, run the installer
15
+
16
+ $ rails g i18n:active_record:install
17
+
18
+ It creates a migration:
19
+
20
+ ```ruby
21
+ class CreateTranslations < ActiveRecord::Migration
22
+ def change
23
+ create_table :translations do |t|
24
+ t.string :locale
25
+ t.string :key
26
+ t.text :value
27
+ t.text :interpolations
28
+ t.boolean :is_proc, default: false
29
+
30
+ t.timestamps
31
+ end
32
+ end
33
+ end
34
+ ```
35
+
36
+ To specify table name use:
37
+
38
+ $ rails g i18n:active_record:install MyTranslation
39
+
40
+ With the translation model you will be able to manage your translation, and add new translations or languages through
41
+ it.
42
+
43
+ By default the installer creates a new file in `config/initializers` named `i18n_active_record.rb` with the following content.
44
+
45
+ ```ruby
46
+ require 'i18n/backend/active_record'
47
+
48
+ Translation = I18n::Backend::ActiveRecord::Translation
49
+
50
+ if Translation.table_exists?
51
+ I18n.backend = I18n::Backend::ActiveRecord.new
52
+
53
+ I18n::Backend::ActiveRecord.send(:include, I18n::Backend::Memoize)
54
+ I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
55
+ I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
56
+
57
+ I18n.backend = I18n::Backend::Chain.new(I18n::Backend::Simple.new, I18n.backend)
58
+ end
59
+ ```
60
+
61
+ To perform a simpler installation use:
62
+
63
+ $ rails g i18n:active_record:install --simple
64
+
65
+ It generates:
66
+
67
+ ```ruby
68
+ require 'i18n/backend/active_record'
69
+ I18n.backend = I18n::Backend::ActiveRecord.new
70
+ ```
71
+
72
+ You may also configure whether the ActiveRecord backend should use `destroy` or `delete` when cleaning up internally.
73
+
74
+ ```ruby
75
+ I18n::Backend::ActiveRecord.configure do |config|
76
+ config.cleanup_with_destroy = true # defaults to false
77
+ end
78
+ ```
79
+
80
+ To configure the ActiveRecord backend to cache translations(might be useful in production) use:
81
+
82
+ ```ruby
83
+ I18n::Backend::ActiveRecord.configure do |config|
84
+ config.cache_translations = true # defaults to false
85
+ end
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ You can now use `I18n.t('Your String')` to lookup translations in the database.
91
+
92
+ ## Missing Translations
93
+
94
+ ### Usage
95
+
96
+ In order to make the `I18n::Backend::ActiveRecord::Missing` module working correctly pluralization rules should be configured properly.
97
+ The `i18n.plural.keys` translation key should be present in any of the backends.
98
+ See https://github.com/svenfuchs/i18n-active_record/blob/master/lib/i18n/backend/active_record/missing.rb for more information.
99
+
100
+ ```yaml
101
+ en:
102
+ i18n:
103
+ plural:
104
+ keys:
105
+ - :zero
106
+ - :one
107
+ - :other
108
+ ```
109
+
110
+ ### Interpolations
111
+
112
+ The `interpolations` field in the `translations` table is used by `I18n::Backend::ActiveRecord::Missing` to store the interpolations seen the first time this Translation was requested. This will help translators understand what interpolations to expect, and thus to include when providing the translations.
113
+
114
+ The `interpolations` field is otherwise unused since the "value" in `Translation#value` is actually used for interpolation during actual translations.
115
+
116
+ ## Examples
117
+
118
+ * http://collectiveidea.com/blog/archives/2016/05/31/beyond-yml-files-dynamic-translations/
119
+
120
+ ## Contributing
121
+
122
+ ### Test suite
123
+
124
+ The test suite can be run with:
125
+
126
+ bundle exec rake
127
+
128
+ By default it runs the tests for SQLite database, to specify a database the `DB` env variable can be used:
129
+
130
+ DB=postgres bundle exec rake
131
+ DB=mysql bundle exec rake
132
+
133
+ To run tests for a specific rails version see [Appraisal](https://github.com/thoughtbot/appraisal):
134
+
135
+ bundle exec appraisal rails-4 rake test
136
+
137
+ ## Maintainers
138
+
139
+ * Sven Fuchs
140
+ * Tim Masliuchenko
data/Rakefile CHANGED
@@ -1,61 +1,12 @@
1
- require 'rake'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'rake/testtask'
3
4
  require 'bundler/gem_tasks'
4
5
 
5
- def execute(command)
6
- puts command
7
- system command
8
- end
9
-
10
- def bundle_options
11
- opt = ''
12
- opt += "--gemfile #{ENV['BUNDLE_GEMFILE']}" if ENV['BUNDLE_GEMFILE']
13
- end
14
-
15
- def each_database(&block)
16
- ['sqlite', 'postgres', 'mysql'].each &block
17
- end
18
-
19
- namespace :bundle do
20
- task :env do
21
- ar = ENV['AR'].to_s
22
-
23
- next if ar.empty?
24
-
25
- gemfile = "gemfiles/Gemfile.rails_#{ar}"
26
- raise "Cannot find gemfile at #{gemfile}" unless File.exist?(gemfile)
27
-
28
- ENV['BUNDLE_GEMFILE'] = gemfile
29
- puts "Using gemfile: #{gemfile}"
30
- end
31
-
32
- task install: :env do
33
- execute "bundle install #{bundle_options}"
34
- end
35
-
36
- task :install_all do
37
- [nil, '3', '4', '5', '6', 'master'].each do |ar|
38
- opt = ar && "AR=#{ar}"
39
- execute "rake bundle:install #{opt}"
40
- end
41
- end
42
- end
43
-
44
- task :test do
45
- each_database { |db| execute "rake #{db}:test" }
46
- end
47
-
48
- Rake::TestTask.new :_test do |t|
6
+ Rake::TestTask.new :test do |t|
49
7
  t.libs << 'test'
50
8
  t.pattern = 'test/**/*_test.rb'
51
9
  t.verbose = false
52
10
  end
53
11
 
54
- each_database do |db|
55
- namespace db do
56
- task(:env) { ENV['DB'] = db }
57
- task test: ['env', 'bundle:env', '_test']
58
- end
59
- end
60
-
61
12
  task default: :test
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators/active_record'
2
4
 
3
5
  module I18n
4
6
  module ActiveRecord
5
7
  module Generators
6
8
  class InstallGenerator < ::ActiveRecord::Generators::Base
7
- desc "Installs i18n-active_record and generates the necessary migrations"
9
+ desc 'Installs i18n-active_record and generates the necessary migrations'
8
10
 
9
11
  argument :name, type: :string, default: 'Translation'
10
12
 
@@ -13,5 +13,6 @@ if Translation.table_exists?
13
13
  end
14
14
 
15
15
  I18n::Backend::ActiveRecord.configure do |config|
16
+ # config.cache_translations = true # defaults to false
16
17
  # config.cleanup_with_destroy = true # defaults to false
17
18
  end
@@ -1,17 +1,13 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
- def self.up
3
- create_table :<%= name.tableize %> do |t|
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
4
  t.string :locale
5
5
  t.string :key
6
- t.text :value
7
- t.text :interpolations
8
- t.boolean :is_proc, :default => false
6
+ t.text :value
7
+ t.text :interpolations
8
+ t.boolean :is_proc, default: false
9
9
 
10
10
  t.timestamps
11
11
  end
12
12
  end
13
-
14
- def self.down
15
- drop_table :<%= name.tableize %>
16
- end
17
13
  end
@@ -2,5 +2,6 @@ require 'i18n/backend/active_record'
2
2
  I18n.backend = I18n::Backend::ActiveRecord.new
3
3
 
4
4
  I18n::Backend::ActiveRecord.configure do |config|
5
+ # config.cache_translations = true # defaults to false
5
6
  # config.cleanup_with_destroy = true # defaults to false
6
7
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module ActiveRecord
3
- VERSION = '0.4.0'
5
+ VERSION = '1.1.0'
4
6
  end
5
7
  end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n'
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Backend
3
5
  class ActiveRecord
4
6
  class Configuration
5
- attr_accessor :cleanup_with_destroy
7
+ attr_accessor :cleanup_with_destroy, :cache_translations
6
8
 
7
9
  def initialize
8
10
  @cleanup_with_destroy = false
11
+ @cache_translations = false
9
12
  end
10
13
  end
11
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This extension stores translation stub records for missing translations to
2
4
  # the database.
3
5
  #
@@ -36,27 +38,28 @@ module I18n
36
38
  include Flatten
37
39
 
38
40
  def store_default_translations(locale, key, options = {})
39
- count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
41
+ count, scope, _, separator = options.values_at(:count, :scope, :default, :separator)
40
42
  separator ||= I18n.default_separator
41
43
  key = normalize_flat_keys(locale, key, scope, separator)
42
44
 
43
- unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
44
- interpolations = options.keys - I18n::RESERVED_KEYS
45
- keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
46
- keys.each { |key| store_default_translation(locale, key, interpolations) }
47
- end
45
+ return if ActiveRecord::Translation.locale(locale).lookup(key).exists?
46
+
47
+ interpolations = options.keys - I18n::RESERVED_KEYS
48
+ keys = count ? I18n.t('i18n.plural.keys', locale: locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
49
+ keys.each { |k| store_default_translation(locale, k, interpolations) }
48
50
  end
49
51
 
50
52
  def store_default_translation(locale, key, interpolations)
51
- translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key
53
+ translation = ActiveRecord::Translation.new locale: locale.to_s, key: key
52
54
  translation.interpolations = interpolations
53
55
  translation.save
54
56
  end
55
57
 
56
58
  def translate(locale, key, options = {})
57
59
  result = catch(:exception) { super }
60
+
58
61
  if result.is_a?(I18n::MissingTranslation)
59
- self.store_default_translations(locale, key, options)
62
+ store_default_translations(locale, key, options)
60
63
  throw(:exception, result)
61
64
  else
62
65
  result
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This module is intended to be mixed into the ActiveRecord backend to allow
2
4
  # storing Ruby Procs as translation values in the database.
3
5
  #
@@ -21,13 +23,13 @@ module I18n
21
23
  module Backend
22
24
  class ActiveRecord
23
25
  module StoreProcs
24
- def value=(v)
25
- case v
26
+ def value=(val)
27
+ case val
26
28
  when Proc
27
- write_attribute(:value, v.to_ruby)
29
+ write_attribute(:value, val.to_ruby)
28
30
  write_attribute(:is_proc, true)
29
31
  else
30
- write_attribute(:value, v)
32
+ write_attribute(:value, val)
31
33
  end
32
34
  end
33
35
 
@@ -36,4 +38,3 @@ module I18n
36
38
  end
37
39
  end
38
40
  end
39
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module I18n
@@ -53,19 +55,20 @@ module I18n
53
55
 
54
56
  serialize :value
55
57
  serialize :interpolations, Array
58
+ after_commit :invalidate_translations_cache
56
59
 
57
60
  class << self
58
61
  def locale(locale)
59
- where(:locale => locale.to_s)
62
+ where(locale: locale.to_s)
60
63
  end
61
64
 
62
65
  def lookup(keys, *separator)
63
66
  column_name = connection.quote_column_name('key')
64
- keys = Array(keys).map! { |key| key.to_s }
67
+ keys = Array(keys).map!(&:to_s)
65
68
 
66
69
  unless separator.empty?
67
- warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
68
- "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
70
+ warn '[DEPRECATION] Giving a separator to Translation.lookup is deprecated. ' \
71
+ 'You can change the internal separator by overwriting FLATTEN_SEPARATOR.'
69
72
  end
70
73
 
71
74
  namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
@@ -83,14 +86,14 @@ module I18n
83
86
  keys.each.with_index.inject(locale_hash) do |iterator, (key_part, index)|
84
87
  key = key_part.to_sym
85
88
  iterator[key] = keys[index + 1] ? (iterator[key] || {}) : t.value
86
- iterator[key]
89
+ iterator[key] # rubocop:disable Lint/UnmodifiedReduceAccumulator
87
90
  end
88
91
  end
89
92
  end
90
93
  end
91
94
 
92
95
  def interpolates?(key)
93
- self.interpolations.include?(key) if self.interpolations
96
+ interpolations&.include?(key)
94
97
  end
95
98
 
96
99
  def value
@@ -107,14 +110,19 @@ module I18n
107
110
  end
108
111
 
109
112
  def value=(value)
110
- if value === false
113
+ case value
114
+ when false
111
115
  value = FALSY_CHAR
112
- elsif value === true
116
+ when true
113
117
  value = TRUTHY_CHAR
114
118
  end
115
119
 
116
120
  write_attribute(:value, value)
117
121
  end
122
+
123
+ def invalidate_translations_cache
124
+ I18n.backend.reload! if I18n::Backend::ActiveRecord.config.cache_translations
125
+ end
118
126
  end
119
127
  end
120
128
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n/backend/base'
2
4
  require 'i18n/backend/active_record/translation'
3
5
 
@@ -19,34 +21,41 @@ module I18n
19
21
  end
20
22
  end
21
23
 
24
+ def initialize
25
+ reload!
26
+ end
27
+
22
28
  module Implementation
23
- include Base, Flatten
29
+ include Base
30
+ include Flatten
24
31
 
25
32
  def available_locales
26
- begin
27
- Translation.available_locales
28
- rescue ::ActiveRecord::StatementInvalid
29
- []
30
- end
33
+ Translation.available_locales
34
+ rescue ::ActiveRecord::StatementInvalid
35
+ []
31
36
  end
32
37
 
33
38
  def store_translations(locale, data, options = {})
34
39
  escape = options.fetch(:escape, true)
40
+
35
41
  flatten_translations(locale, data, escape, false).each do |key, value|
36
42
  translation = Translation.locale(locale).lookup(expand_keys(key))
37
43
 
38
- if ActiveRecord.config.cleanup_with_destroy
44
+ if self.class.config.cleanup_with_destroy
39
45
  translation.destroy_all
40
46
  else
41
47
  translation.delete_all
42
48
  end
43
49
 
44
- Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
50
+ Translation.create(locale: locale.to_s, key: key.to_s, value: value)
45
51
  end
52
+
53
+ reload! if self.class.config.cache_translations
46
54
  end
47
55
 
48
56
  def reload!
49
57
  @translations = nil
58
+
50
59
  self
51
60
  end
52
61
 
@@ -63,15 +72,17 @@ module I18n
63
72
  @translations ||= {}
64
73
  end
65
74
 
66
- protected
75
+ protected
67
76
 
68
77
  def lookup(locale, key, scope = [], options = {})
69
78
  key = normalize_flat_keys(locale, key, scope, options[:separator])
70
- if key.first == '.'
71
- key = key[1..-1]
72
- end
73
- if key.last == '.'
74
- key = key[0..-2]
79
+ key = key[1..-1] if key.first == '.'
80
+ key = key[0..-2] if key.last == '.'
81
+
82
+ if self.class.config.cache_translations
83
+ keys = ([locale] + key.split(I18n::Backend::Flatten::FLATTEN_SEPARATOR)).map(&:to_sym)
84
+
85
+ return translations.dig(*keys)
75
86
  end
76
87
 
77
88
  result = if key == ''
@@ -94,23 +105,25 @@ module I18n
94
105
 
95
106
  def build_translation_hash_by_key(lookup_key, translation)
96
107
  hash = {}
97
- if lookup_key == ''
98
- chop_range = 0..-1
108
+
109
+ chop_range = if lookup_key == ''
110
+ 0..-1
99
111
  else
100
- chop_range = (lookup_key.size + FLATTEN_SEPARATOR.size)..-1
112
+ (lookup_key.size + FLATTEN_SEPARATOR.size)..-1
101
113
  end
102
114
  translation_nested_keys = translation.key.slice(chop_range).split(FLATTEN_SEPARATOR)
103
115
  translation_nested_keys.each.with_index.inject(hash) do |iterator, (key, index)|
104
116
  iterator[key] = translation_nested_keys[index + 1] ? {} : translation.value
105
117
  iterator[key]
106
118
  end
119
+
107
120
  hash
108
121
  end
109
122
 
110
123
  # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
111
124
  def expand_keys(key)
112
- key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
113
- keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
125
+ key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, k|
126
+ keys << [keys.last, k].compact.join(FLATTEN_SEPARATOR)
114
127
  end
115
128
  end
116
129
  end