diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..c7d01e20 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: CI Tests + +on: + push: + branches: [master] + pull_request: + branches: ['**'] + +jobs: + test: + services: + postgres: + image: postgres + env: + POSTGRES_DB: i18n_unittest + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + mysql: + image: mysql:5.7 + env: + MYSQL_DATABASE: i18n_unittest + MYSQL_ALLOW_EMPTY_PASSWORD: true + ports: + - '3306:3306' + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + strategy: + fail-fast: true + matrix: + ruby: [2.4, 2.5, 2.6, 2.7, 3.0, 'head'] + rails: [4, 5, 6, 'head'] + exclude: + - ruby: 2.4 + rails: 6 + - ruby: 2.4 + rails: head + - ruby: 2.5 + rails: head + - ruby: 2.6 + rails: head + - ruby: 2.7 + rails: 4 + - ruby: 3.0 + rails: 4 + - ruby: 3.0 + rails: 5 + - ruby: head + rails: 4 + - ruby: head + rails: 5 + name: 'Ruby: ${{ matrix.ruby }}, Rails: ${{ matrix.rails }}' + runs-on: ubuntu-latest + env: + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/Gemfile.rails_${{ matrix.rails }} + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + cache-version: 1 + - name: Run tests for SQLite + run: bundle exec rake + - name: Run tests for PostgreSQL + env: + DB: postgres + run: bundle exec rake + - name: Run tests for MySQL + env: + DB: mysql + run: bundle exec rake diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f6308e71..00000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: ruby -cache: bundler - -notifications: - email: false - -services: - - postgresql - - mysql - -before_install: - - gem install bundler -v 2.0.2 - -before_script: - - mysql -e 'create database i18n_unittest;' - - psql -c 'create database i18n_unittest;' -U postgres - -rvm: - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - -gemfile: - - gemfiles/Gemfile.rails_3 - - gemfiles/Gemfile.rails_4 - - gemfiles/Gemfile.rails_5 - - gemfiles/Gemfile.rails_6 - - gemfiles/Gemfile.rails_head - -env: - - PG_USER=postgres - -matrix: - fast_finish: true - exclude: - - rvm: 2.3 - gemfile: gemfiles/Gemfile.rails_6 - - rvm: 2.4 - gemfile: gemfiles/Gemfile.rails_6 - - rvm: 2.3 - gemfile: gemfiles/Gemfile.rails_head - - rvm: 2.4 - gemfile: gemfiles/Gemfile.rails_head - - rvm: 2.5 - gemfile: gemfiles/Gemfile.rails_head - - rvm: 2.6 - gemfile: gemfiles/Gemfile.rails_head diff --git a/Gemfile.lock b/Gemfile.lock index 8990895c..482fc350 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - i18n-active_record (0.4.0) + i18n-active_record (0.4.1) i18n (>= 0.5.0) GEM @@ -22,12 +22,12 @@ GEM i18n (1.8.10) concurrent-ruby (~> 1.0) minitest (5.14.4) - mocha (1.12.0) + mocha (1.13.0) mysql2 (0.5.3) pg (1.2.3) - rake (13.0.3) + rake (13.0.1) sqlite3 (1.4.2) - test_declarative (0.0.6) + test_declarative (0.0.5) tzinfo (2.0.4) concurrent-ruby (~> 1.0) zeitwerk (2.4.2) @@ -48,4 +48,4 @@ DEPENDENCIES test_declarative BUNDLED WITH - 2.1.4 + 2.2.29 diff --git a/README.md b/README.md index a1f65061..96f6b887 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# I18n::Backend::ActiveRecord +# I18n::Backend::ActiveRecord ![Build Status](https://github.com/svenfuchs/i18n-active_record/actions/workflows/test.yml/badge.svg) This repository contains the I18n ActiveRecord backend and support code that has been extracted from the "I18n": http://github.com/svenfuchs/i18n. -It is fully compatible with Rails 3, 4, 5 and 6. +It is fully compatible with Rails 4, 5 and 6. ## Installation @@ -77,11 +77,37 @@ I18n::Backend::ActiveRecord.configure do |config| end ``` +To configure the ActiveRecord backend to cache translations(might be useful in production) use: + +```ruby +I18n::Backend::ActiveRecord.configure do |config| + config.cache_translations = true # defaults to false +end +``` + ## Usage You can now use `I18n.t('Your String')` to lookup translations in the database. -## Missing Translations -> Interpolations +## Missing Translations + +### Usage + +In order to make the `I18n::Backend::ActiveRecord::Missing` module working correctly pluralization rules should be configured properly. +The `i18n.plural.keys` translation key should be present in any of the backends. +(See https://github.com/svenfuchs/i18n-active_record/blob/master/lib/i18n/backend/active_record/missing.rb for more information) + +```yaml +en: + i18n: + plural: + keys: + - :zero + - :one + - :other +``` + +### Interpolations 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. @@ -93,7 +119,20 @@ The `interpolations` field is otherwise unused since the "value" in `Translation ## Contributing -To run the test suite for all databases use `rake test` or using only SQLite with `rake sqlite:test` +### Test suite + +The test suite can be run with: + + bundle exec rake + +By default it runs the tests for SQLite database, to specify a database the `DB` env variable can be used: + + DB=postgres bundle exec rake + DB=mysql bundle exec rake + +There are multiple gemfiles(mostly used for CI) and they can be activated with the `--gemfile` option: + + bundle exec --gemfile gemfiles/Gemfile.rails_4 rake ## Maintainers diff --git a/Rakefile b/Rakefile index f8923285..4f45d1cc 100644 --- a/Rakefile +++ b/Rakefile @@ -1,66 +1,9 @@ -require 'rake' require 'rake/testtask' -require 'bundler/gem_tasks' -def execute(command) - puts command - system command -end - -def bundle_options - return '' unless ENV['BUNDLE_GEMFILE'] - - "--gemfile #{ENV['BUNDLE_GEMFILE']}" -end - -def each_database(&block) - ['sqlite', 'postgres', 'mysql'].each &block -end - -namespace :bundle do - task :env do - ar = ENV['AR'].to_s - - next if ar.empty? - - gemfile = "gemfiles/Gemfile.rails_#{ar}" - raise "Cannot find gemfile at #{gemfile}" unless File.exist?(gemfile) - - ENV['BUNDLE_GEMFILE'] = gemfile - puts "Using gemfile: #{gemfile}" - end - - task install: :env do - execute "bundle install #{bundle_options}" - end - - task update: :env do - execute "bundle update #{bundle_options}" - end - - task :install_all do - [nil, '3', '4', '5', '6', 'head'].each do |ar| - opt = ar && "AR=#{ar}" - execute "rake bundle:install #{opt}" - end - end -end - -task :test do - each_database { |db| execute "rake #{db}:test" } -end - -Rake::TestTask.new :_test do |t| +Rake::TestTask.new :test do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.verbose = false end -each_database do |db| - namespace db do - task(:env) { ENV['DB'] = db } - task test: ['env', 'bundle:env', '_test'] - end -end - task default: :test diff --git a/gemfiles/Gemfile.rails_3 b/gemfiles/Gemfile.rails_3 deleted file mode 100644 index fbb82cfc..00000000 --- a/gemfiles/Gemfile.rails_3 +++ /dev/null @@ -1,13 +0,0 @@ -source 'https://rubygems.org' - -gemspec path: '..' - -gem 'activerecord', '~> 3.2.0' -gem 'sqlite3' -gem 'mysql2' -gem 'pg', '~> 0.18.0' - -gem 'minitest' -gem 'test_declarative' -gem 'mocha' -gem 'rake' diff --git a/gemfiles/Gemfile.rails_3.lock b/gemfiles/Gemfile.rails_3.lock deleted file mode 100644 index ce2906cd..00000000 --- a/gemfiles/Gemfile.rails_3.lock +++ /dev/null @@ -1,52 +0,0 @@ -PATH - remote: .. - specs: - i18n-active_record (0.4.0) - i18n (>= 0.5.0) - -GEM - remote: https://rubygems.org/ - specs: - activemodel (3.2.22.5) - activesupport (= 3.2.22.5) - builder (~> 3.0.0) - activerecord (3.2.22.5) - activemodel (= 3.2.22.5) - activesupport (= 3.2.22.5) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activesupport (3.2.22.5) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) - arel (3.0.3) - builder (3.0.4) - i18n (0.8.1) - metaclass (0.0.4) - minitest (5.10.1) - mocha (1.2.1) - metaclass (~> 0.0.1) - multi_json (1.12.1) - mysql2 (0.4.5) - pg (0.18.4) - rake (13.0.1) - sqlite3 (1.3.13) - test_declarative (0.0.5) - tzinfo (0.3.52) - -PLATFORMS - ruby - -DEPENDENCIES - activerecord (~> 3.2.0) - bundler - i18n-active_record! - minitest - mocha - mysql2 - pg (~> 0.18.0) - rake - sqlite3 - test_declarative - -BUNDLED WITH - 2.0.2 diff --git a/gemfiles/Gemfile.rails_4.lock b/gemfiles/Gemfile.rails_4.lock index 348a0081..00d7f045 100644 --- a/gemfiles/Gemfile.rails_4.lock +++ b/gemfiles/Gemfile.rails_4.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - i18n-active_record (0.4.0) + i18n-active_record (0.4.1) i18n (>= 0.5.0) GEM @@ -22,11 +22,9 @@ GEM arel (6.0.4) builder (3.2.3) i18n (0.8.1) - metaclass (0.0.4) minitest (5.10.1) - mocha (1.2.1) - metaclass (~> 0.0.1) - mysql2 (0.4.5) + mocha (1.13.0) + mysql2 (0.4.10) pg (0.18.4) rake (13.0.1) sqlite3 (1.3.13) diff --git a/gemfiles/Gemfile.rails_5.lock b/gemfiles/Gemfile.rails_5.lock index 189dfa1c..2a99ed44 100644 --- a/gemfiles/Gemfile.rails_5.lock +++ b/gemfiles/Gemfile.rails_5.lock @@ -1,32 +1,32 @@ PATH remote: .. specs: - i18n-active_record (0.4.0) + i18n-active_record (0.4.1) i18n (>= 0.5.0) GEM remote: https://rubygems.org/ specs: - activemodel (5.2.4.5) - activesupport (= 5.2.4.5) - activerecord (5.2.4.5) - activemodel (= 5.2.4.5) - activesupport (= 5.2.4.5) + activemodel (5.2.6) + activesupport (= 5.2.6) + activerecord (5.2.6) + activemodel (= 5.2.6) + activesupport (= 5.2.6) arel (>= 9.0) - activesupport (5.2.4.5) + activesupport (5.2.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) arel (9.0.0) - concurrent-ruby (1.1.8) - i18n (1.8.9) + concurrent-ruby (1.1.9) + i18n (1.8.10) concurrent-ruby (~> 1.0) minitest (5.14.4) - mocha (1.12.0) + mocha (1.13.0) mysql2 (0.5.3) pg (1.2.3) - rake (13.0.3) + rake (13.0.6) sqlite3 (1.4.2) test_declarative (0.0.6) thread_safe (0.3.6) @@ -49,4 +49,4 @@ DEPENDENCIES test_declarative BUNDLED WITH - 2.1.4 + 2.2.16 diff --git a/gemfiles/Gemfile.rails_6.lock b/gemfiles/Gemfile.rails_6.lock index 7b6c249a..0ba96ee4 100644 --- a/gemfiles/Gemfile.rails_6.lock +++ b/gemfiles/Gemfile.rails_6.lock @@ -1,36 +1,36 @@ PATH remote: .. specs: - i18n-active_record (0.4.0) + i18n-active_record (0.4.1) i18n (>= 0.5.0) GEM remote: https://rubygems.org/ specs: - activemodel (6.1.3) - activesupport (= 6.1.3) - activerecord (6.1.3) - activemodel (= 6.1.3) - activesupport (= 6.1.3) - activesupport (6.1.3) + activemodel (6.1.4.1) + activesupport (= 6.1.4.1) + activerecord (6.1.4.1) + activemodel (= 6.1.4.1) + activesupport (= 6.1.4.1) + activesupport (6.1.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - concurrent-ruby (1.1.8) - i18n (1.8.9) + concurrent-ruby (1.1.9) + i18n (1.8.10) concurrent-ruby (~> 1.0) minitest (5.14.4) - mocha (1.12.0) + mocha (1.13.0) mysql2 (0.5.3) pg (1.2.3) - rake (13.0.3) + rake (13.0.6) sqlite3 (1.4.2) test_declarative (0.0.6) tzinfo (2.0.4) concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) + zeitwerk (2.5.1) PLATFORMS ruby @@ -48,4 +48,4 @@ DEPENDENCIES test_declarative BUNDLED WITH - 2.1.4 + 2.2.16 diff --git a/gemfiles/Gemfile.rails_head.lock b/gemfiles/Gemfile.rails_head.lock index c698fac9..2f154b3e 100644 --- a/gemfiles/Gemfile.rails_head.lock +++ b/gemfiles/Gemfile.rails_head.lock @@ -1,42 +1,40 @@ GIT remote: https://github.com/rails/rails - revision: a8c462d37c1e482fa0a0fc16014fc9ad1d97032f + revision: 2451a568aff34816f1df7bd92b158fb059771337 branch: main specs: - activemodel (7.0.0.alpha) - activesupport (= 7.0.0.alpha) - activerecord (7.0.0.alpha) - activemodel (= 7.0.0.alpha) - activesupport (= 7.0.0.alpha) - activesupport (7.0.0.alpha) + activemodel (7.0.0.alpha2) + activesupport (= 7.0.0.alpha2) + activerecord (7.0.0.alpha2) + activemodel (= 7.0.0.alpha2) + activesupport (= 7.0.0.alpha2) + activesupport (7.0.0.alpha2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) PATH remote: .. specs: - i18n-active_record (0.4.0) + i18n-active_record (0.4.1) i18n (>= 0.5.0) GEM remote: https://rubygems.org/ specs: - concurrent-ruby (1.1.8) - i18n (1.8.9) + concurrent-ruby (1.1.9) + i18n (1.8.10) concurrent-ruby (~> 1.0) minitest (5.14.4) - mocha (1.12.0) + mocha (1.13.0) mysql2 (0.5.3) pg (1.2.3) - rake (13.0.3) + rake (13.0.6) sqlite3 (1.4.2) test_declarative (0.0.6) tzinfo (2.0.4) concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) PLATFORMS ruby @@ -54,4 +52,4 @@ DEPENDENCIES test_declarative BUNDLED WITH - 2.1.4 + 2.2.16 diff --git a/lib/i18n/backend/active_record.rb b/lib/i18n/backend/active_record.rb index 15e5042c..58a4489d 100644 --- a/lib/i18n/backend/active_record.rb +++ b/lib/i18n/backend/active_record.rb @@ -9,6 +9,9 @@ class ActiveRecord autoload :Translation, 'i18n/backend/active_record/translation' autoload :Configuration, 'i18n/backend/active_record/configuration' + include Base + include Flatten + class << self def configure yield(config) if block_given? @@ -19,114 +22,111 @@ def config end end - module Implementation - include Base, Flatten + def initialize(*args) + super - def available_locales - begin - Translation.available_locales - rescue ::ActiveRecord::StatementInvalid - [] - end - end + reload! + end - def store_translations(locale, data, options = {}) - escape = options.fetch(:escape, true) - flatten_translations(locale, data, escape, false).each do |key, value| - translation = Translation.locale(locale).lookup(expand_keys(key)) + def available_locales + Translation.available_locales + rescue ::ActiveRecord::StatementInvalid + [] + end - if ActiveRecord.config.cleanup_with_destroy - translation.destroy_all - else - translation.delete_all - end + def store_translations(locale, data, options = {}) + escape = options.fetch(:escape, true) - Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value) + flatten_translations(locale, data, escape, false).each do |key, value| + translation = Translation.locale(locale).lookup(expand_keys(key)) + + if ActiveRecord.config.cleanup_with_destroy + translation.destroy_all + else + translation.delete_all end - reload! if ActiveRecord.config.cache_translations + Translation.create(locale: locale.to_s, key: key.to_s, value: value) end - def reload! - @translations = nil + reload! if ActiveRecord.config.cache_translations + end - self - end + def reload! + @translations = nil - def initialized? - !@translations.nil? - end + self + end - def init_translations - @translations = Translation.to_hash - end + def initialized? + !@translations.nil? + end - def translations(do_init: false) - init_translations if do_init || !initialized? - @translations ||= {} - end + def init_translations + @translations = Translation.to_hash + end - protected + def translations(do_init: false) + init_translations if do_init || !initialized? + @translations ||= {} + end - def lookup(locale, key, scope = [], options = {}) - key = normalize_flat_keys(locale, key, scope, options[:separator]) - if key.first == '.' - key = key[1..-1] - end - if key.last == '.' - key = key[0..-2] - end + protected - if ActiveRecord.config.cache_translations - init_translations if @translations.nil? || @translations.empty? + def lookup(locale, key, scope = [], options = {}) + key = normalize_flat_keys(locale, key, scope, options[:separator]) + key = key[1..-1] if key.first == '.' + key = key[0..-2] if key.last == '.' - keys = ([locale] + key.split(I18n::Backend::Flatten::FLATTEN_SEPARATOR)).map(&:to_sym) + if ActiveRecord.config.cache_translations + init_translations if @translations.nil? || @translations.empty? - return translations.dig(*keys) - end + keys = ([locale] + key.split(I18n::Backend::Flatten::FLATTEN_SEPARATOR)).map(&:to_sym) - result = if key == '' - Translation.locale(locale).all - else - Translation.locale(locale).lookup(key) - end + return translations.dig(*keys) + end - if result.empty? - nil - elsif result.first.key == key - result.first.value - else - result = result.inject({}) do |hash, translation| - hash.deep_merge build_translation_hash_by_key(key, translation) - end - result.deep_symbolize_keys - end + result = if key == '' + Translation.locale(locale).all + else + Translation.locale(locale).lookup(key) end - def build_translation_hash_by_key(lookup_key, translation) - hash = {} - if lookup_key == '' - chop_range = 0..-1 - else - chop_range = (lookup_key.size + FLATTEN_SEPARATOR.size)..-1 + if result.empty? + nil + elsif result.first.key == key + result.first.value + else + result = result.inject({}) do |hash, translation| + hash.deep_merge build_translation_hash_by_key(key, translation) end - translation_nested_keys = translation.key.slice(chop_range).split(FLATTEN_SEPARATOR) - translation_nested_keys.each.with_index.inject(hash) do |iterator, (key, index)| - iterator[key] = translation_nested_keys[index + 1] ? {} : translation.value - iterator[key] - end - hash + result.deep_symbolize_keys end + end - # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz'] - def expand_keys(key) - key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key| - keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR) - end + def build_translation_hash_by_key(lookup_key, translation) + hash = {} + + chop_range = if lookup_key == '' + 0..-1 + else + (lookup_key.size + FLATTEN_SEPARATOR.size)..-1 end + translation_nested_keys = translation.key.slice(chop_range).split(FLATTEN_SEPARATOR) + translation_nested_keys.each.with_index.inject(hash) do |iterator, (key, index)| + iterator[key] = translation_nested_keys[index + 1] ? {} : translation.value + iterator[key] + end + + hash end - include Implementation + # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz'] + def expand_keys(key) + key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, k| + keys << [keys.last, k].compact.join(FLATTEN_SEPARATOR) + end + end end end end diff --git a/lib/i18n/backend/active_record/configuration.rb b/lib/i18n/backend/active_record/configuration.rb index 4d4176c9..f3f72fb1 100644 --- a/lib/i18n/backend/active_record/configuration.rb +++ b/lib/i18n/backend/active_record/configuration.rb @@ -2,8 +2,7 @@ module I18n module Backend class ActiveRecord class Configuration - attr_accessor :cleanup_with_destroy - attr_accessor :cache_translations + attr_accessor :cleanup_with_destroy, :cache_translations def initialize @cleanup_with_destroy = false diff --git a/lib/i18n/backend/active_record/missing.rb b/lib/i18n/backend/active_record/missing.rb index b04e92f7..7f3db4e0 100644 --- a/lib/i18n/backend/active_record/missing.rb +++ b/lib/i18n/backend/active_record/missing.rb @@ -36,27 +36,28 @@ module Missing include Flatten def store_default_translations(locale, key, options = {}) - count, scope, default, separator = options.values_at(:count, :scope, :default, :separator) + count, scope, _, separator = options.values_at(:count, :scope, :default, :separator) separator ||= I18n.default_separator key = normalize_flat_keys(locale, key, scope, separator) - unless ActiveRecord::Translation.locale(locale).lookup(key).exists? - interpolations = options.keys - I18n::RESERVED_KEYS - keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key] - keys.each { |key| store_default_translation(locale, key, interpolations) } - end + return if ActiveRecord::Translation.locale(locale).lookup(key).exists? + + interpolations = options.keys - I18n::RESERVED_KEYS + keys = count ? I18n.t('i18n.plural.keys', locale: locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key] + keys.each { |k| store_default_translation(locale, k, interpolations) } end def store_default_translation(locale, key, interpolations) - translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key + translation = ActiveRecord::Translation.new locale: locale.to_s, key: key translation.interpolations = interpolations translation.save end def translate(locale, key, options = {}) result = catch(:exception) { super } + if result.is_a?(I18n::MissingTranslation) - self.store_default_translations(locale, key, options) + store_default_translations(locale, key, options) throw(:exception, result) else result diff --git a/lib/i18n/backend/active_record/translation.rb b/lib/i18n/backend/active_record/translation.rb index c3b7e616..7d990240 100644 --- a/lib/i18n/backend/active_record/translation.rb +++ b/lib/i18n/backend/active_record/translation.rb @@ -57,12 +57,12 @@ class Translation < ::ActiveRecord::Base class << self def locale(locale) - where(:locale => locale.to_s) + where(locale: locale.to_s) end def lookup(keys, *separator) column_name = connection.quote_column_name('key') - keys = Array(keys).map! { |key| key.to_s } + keys = Array(keys).map!(&:to_s) unless separator.empty? warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " << @@ -91,7 +91,7 @@ def to_hash end def interpolates?(key) - self.interpolations.include?(key) if self.interpolations + interpolations&.include?(key) end def value diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 8b3dc9cc..9b449e0d 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -4,13 +4,15 @@ class I18nBackendActiveRecordTest < I18n::TestCase def setup super + I18n::Backend::ActiveRecord::Translation.destroy_all I18n.backend = I18n::Backend::ActiveRecord.new - store_translations(:en, :foo => { :bar => 'bar', :baz => 'baz' }) + + store_translations(:en, foo: { bar: 'bar', baz: 'baz' }) end def teardown - I18n::Backend::ActiveRecord::Translation.destroy_all I18n::Backend::ActiveRecord.instance_variable_set :@config, I18n::Backend::ActiveRecord::Configuration.new + super end @@ -22,22 +24,20 @@ def setup end test "store_translations does not allow ambiguous keys (1)" do - I18n::Backend::ActiveRecord::Translation.delete_all - I18n.backend.store_translations(:en, :foo => 'foo') - I18n.backend.store_translations(:en, :foo => { :bar => 'bar' }) - I18n.backend.store_translations(:en, :foo => { :baz => 'baz' }) + store_translations(:en, foo: 'foo') + store_translations(:en, foo: { bar: 'bar' }) + store_translations(:en, foo: { baz: 'baz' }) translations = I18n::Backend::ActiveRecord::Translation.locale(:en).lookup('foo').all assert_equal %w(bar baz), translations.map(&:value) - assert_equal({ :bar => 'bar', :baz => 'baz' }, I18n.t(:foo)) + assert_equal({ bar: 'bar', baz: 'baz' }, I18n.t(:foo)) end test "store_translations does not allow ambiguous keys (2)" do - I18n::Backend::ActiveRecord::Translation.delete_all - I18n.backend.store_translations(:en, :foo => { :bar => 'bar' }) - I18n.backend.store_translations(:en, :foo => { :baz => 'baz' }) - I18n.backend.store_translations(:en, :foo => 'foo') + store_translations(:en, foo: { bar: 'bar' }) + store_translations(:en, foo: { baz: 'baz' }) + store_translations(:en, foo: 'foo') translations = I18n::Backend::ActiveRecord::Translation.locale(:en).lookup('foo').all assert_equal %w(foo), translations.map(&:value) @@ -46,8 +46,9 @@ def setup end test "can store translations with keys that are translations containing special chars" do - I18n.backend.store_translations(:es, :"Pagina's" => "Pagina's" ) - assert_equal "Pagina's", I18n.t(:"Pagina's", :locale => :es) + store_translations(:es, "Pagina's": "Pagina's") + + assert_equal "Pagina's", I18n.t(:"Pagina's", locale: :es) end test "missing translations table does not cause an error in #available_locales" do @@ -60,12 +61,11 @@ def setup end test "available_locales returns uniq locales" do - I18n::Backend::ActiveRecord::Translation.delete_all - I18n.backend.store_translations(:en, :foo => { :bar => 'bar' }) - I18n.backend.store_translations(:en, :foo => { :baz => 'baz' }) - I18n.backend.store_translations(:de, :foo1 => 'foo') - I18n.backend.store_translations(:de, :foo2 => 'foo') - I18n.backend.store_translations(:uk, :foo3 => 'foo') + store_translations(:en, foo: { bar: 'bar' }) + store_translations(:en, foo: { baz: 'baz' }) + store_translations(:de, foo1: 'foo') + store_translations(:de, foo2: 'foo') + store_translations(:uk, foo3: 'foo') available_locales = I18n::Backend::ActiveRecord::Translation.available_locales assert_equal 3, available_locales.size @@ -87,24 +87,27 @@ def setup end test "fetching subtree of translations" do - I18n::Backend::ActiveRecord::Translation.delete_all - I18n.backend.store_translations(:en, foo: { bar: { fizz: 'buzz', spuz: 'zazz' }, baz: { fizz: 'buzz' } }) + store_translations(:en, foo: { bar: { fizz: 'buzz', spuz: 'zazz' }, baz: { fizz: 'buzz' } }) + assert_equal I18n.t(:foo), { bar: { fizz: 'buzz', spuz: 'zazz' }, baz: { fizz: 'buzz' } } end test "build_translation_hash_by_key" do translation = I18n::Backend::ActiveRecord::Translation.new(value: 'translation', key: 'foo.bar.fizz.buzz') expected_hash = { 'bar' => { 'fizz' => { 'buzz' => 'translation' } } } + assert_equal I18n.backend.send(:build_translation_hash_by_key, 'foo', translation), expected_hash end test "returning all keys via ." do - expected_hash = {:foo => { :bar => 'bar', :baz => 'baz' }} + expected_hash = { foo: { bar: 'bar', baz: 'baz' }} + assert_equal expected_hash, I18n.t('.') end test "accessing keys with a trailing/leading period" do - expected_hash = { :bar => 'bar', :baz => 'baz' } + expected_hash = { bar: 'bar', baz: 'baz' } + assert_equal expected_hash, I18n.t('foo') assert_equal expected_hash, I18n.t('.foo') assert_equal expected_hash, I18n.t('foo.') @@ -121,23 +124,28 @@ def setup test "intially unitinitialized" do refute I18n.backend.initialized? + I18n.backend.init_translations assert I18n.backend.initialized? + I18n.backend.reload! refute I18n.backend.initialized? + I18n.backend.init_translations assert I18n.backend.initialized? end test "translations returns all translations" do - expected_hash = { :en => { :foo => { :bar => 'bar', :baz => 'baz' } } } + expected_hash = { en: { foo: { bar: 'bar', baz: 'baz' } } } I18n.backend.init_translations + assert_equal expected_hash, I18n.backend.send(:translations) assert I18n.backend.initialized? end test "translations initialized with do_init argument" do - expected_hash = { :en => { :foo => { :bar => 'bar', :baz => 'baz' } } } + expected_hash = { en: { foo: { bar: 'bar', baz: 'baz' } } } + refute I18n.backend.initialized? assert_equal expected_hash, I18n.backend.send(:translations, do_init: true) assert I18n.backend.initialized? diff --git a/test/api_test.rb b/test/api_test.rb index 26164463..8c072716 100644 --- a/test/api_test.rb +++ b/test/api_test.rb @@ -3,6 +3,7 @@ class I18nActiveRecordApiTest < I18n::TestCase def setup I18n.backend = I18n::Backend::ActiveRecord.new + super end diff --git a/test/missing_test.rb b/test/missing_test.rb index 3b68e88e..bd4a8f46 100644 --- a/test/missing_test.rb +++ b/test/missing_test.rb @@ -8,13 +8,15 @@ class BackendWithMissing < I18n::Backend::ActiveRecord def setup super + I18n::Backend::ActiveRecord::Translation.destroy_all I18n.backend = BackendWithMissing.new - store_translations(:en, :bar => 'Bar', :i18n => { :plural => { :keys => [:zero, :one, :other] } }) + + store_translations(:en, bar: 'Bar', i18n: { plural: { keys: [:zero, :one, :other] } }) end def teardown - I18n::Backend::ActiveRecord::Translation.destroy_all I18n::Backend::ActiveRecord.instance_variable_set :@config, I18n::Backend::ActiveRecord::Configuration.new + super end @@ -26,9 +28,10 @@ def setup end test "can persist interpolations" do - translation = I18n::Backend::ActiveRecord::Translation.new(:key => 'foo', :value => 'bar', :locale => :en) + translation = I18n::Backend::ActiveRecord::Translation.new(key: 'foo', value: 'bar', locale: :en) translation.interpolations = %w(count name) translation.save + assert translation.valid? end @@ -41,45 +44,52 @@ def setup test "lookup does not persist the key twice" do 2.times { I18n.t('foo.bar.baz') } + assert_equal 3, I18n::Backend::ActiveRecord::Translation.count assert I18n::Backend::ActiveRecord::Translation.locale(:en).find_by_key('foo.bar.baz') end test "lookup persists interpolation keys when looked up directly" do - I18n.t('foo.bar.baz', :cow => "lucy" ) # creates stub translation. + I18n.t('foo.bar.baz', cow: "lucy" ) # creates stub translation. translation_stub = I18n::Backend::ActiveRecord::Translation.locale(:en).lookup('foo.bar.baz').first + assert translation_stub.interpolates?(:cow) end test "creates one stub per pluralization" do - I18n.t('foo', :count => 999) + I18n.t('foo', count: 999) translations = I18n::Backend::ActiveRecord::Translation.locale(:en).where key: %w{ foo.zero foo.one foo.other } + assert_equal 3, translations.length end test "creates no stub for base key in pluralization" do - I18n.t('foo', :count => 999) + I18n.t('foo', count: 999) + assert_equal 3, I18n::Backend::ActiveRecord::Translation.locale(:en).lookup("foo").count assert !I18n::Backend::ActiveRecord::Translation.locale(:en).find_by_key("foo") end test "creates a stub when a custom separator is used" do - I18n.t('foo|baz', :separator => '|') - I18n::Backend::ActiveRecord::Translation.locale(:en).lookup("foo.baz").first.update(:value => 'baz!') - assert_equal 'baz!', I18n.t('foo|baz', :separator => '|') + I18n.t('foo|baz', separator: '|') + I18n::Backend::ActiveRecord::Translation.locale(:en).lookup("foo.baz").first.update(value: 'baz!') + + assert_equal 'baz!', I18n.t('foo|baz', separator: '|') end test "creates a stub per pluralization when a custom separator is used" do - I18n.t('foo|bar', :count => 999, :separator => '|') + I18n.t('foo|bar', count: 999, separator: '|') translations = I18n::Backend::ActiveRecord::Translation.locale(:en).where key: %w{ foo.bar.zero foo.bar.one foo.bar.other } + assert_equal 3, translations.length end test "creates a stub when a custom separator is used and the key contains the flatten separator (a dot character)" do key = 'foo|baz.zab' - I18n.t(key, :separator => '|') - I18n::Backend::ActiveRecord::Translation.locale(:en).lookup("foo.baz\001zab").first.update(:value => 'baz!') - assert_equal 'baz!', I18n.t(key, :separator => '|') + I18n.t(key, separator: '|') + I18n::Backend::ActiveRecord::Translation.locale(:en).lookup("foo.baz\001zab").first.update(value: 'baz!') + + assert_equal 'baz!', I18n.t(key, separator: '|') end end diff --git a/test/test_helper.rb b/test/test_helper.rb index f3197f2e..8039c70f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -15,34 +15,48 @@ puts "can't use ActiveRecord backend because: #{e.message}" rescue ::ActiveRecord::ConnectionNotEstablished require 'i18n/backend/active_record' + case ENV['DB'] when 'postgres' - ::ActiveRecord::Base.establish_connection adapter: 'postgresql', database: 'i18n_unittest', username: ENV['PG_USER'] || 'i18n', password: '', host: 'localhost' + ::ActiveRecord::Base.establish_connection( + adapter: 'postgresql', + database: 'i18n_unittest', + username: ENV['PG_USER'] || 'postgres', + password: ENV['PG_PASSWORD'] || 'postgres', + host: 'localhost' + ) when 'mysql' - ::ActiveRecord::Base.establish_connection adapter: 'mysql2', database: 'i18n_unittest', username: 'root', password: '', host: 'localhost' + ::ActiveRecord::Base.establish_connection( + adapter: 'mysql2', + database: 'i18n_unittest', + username: ENV['MYSQL_USER'] || 'root', + password: ENV['MYSQL_PASSWORD'] || '', + host: '127.0.0.1' + ) else ::ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:' end + ::ActiveRecord::Migration.verbose = false - ::ActiveRecord::Schema.define(:version => 1) do - create_table :translations, :force => true do |t| + ::ActiveRecord::Schema.define(version: 1) do + create_table :translations, force: true do |t| t.string :locale t.string :key t.text :value t.text :interpolations - t.boolean :is_proc, :default => false + t.boolean :is_proc, default: false end - add_index :translations, [:locale, :key], :unique => true + add_index :translations, [:locale, :key], unique: true end end TEST_CASE = defined?(Minitest::Test) ? Minitest::Test : MiniTest::Unit::TestCase class TEST_CASE - alias :assert_raise :assert_raises - alias :assert_not_equal :refute_equal + alias assert_raise assert_raises + alias assert_not_equal refute_equal - def assert_nothing_raised(*args) + def assert_nothing_raised(*_args) yield end end