Skip to content

Commit

Permalink
adding i18n support
Browse files Browse the repository at this point in the history
This adds translations for filter types, interaction names, attribute
names, and validation errors. All of them are scoped to
:active_interaction.
  • Loading branch information
AaronLasseigne committed Aug 13, 2013
1 parent 143e29b commit 906fff7
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 4 deletions.
2 changes: 2 additions & 0 deletions lib/active_interaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
require 'active_interaction/filters/time_filter'
require 'active_interaction/base'

I18n.backend.load_translations(Dir['lib/active_interaction/locale/*.yml'])

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 14, 2013

Collaborator

This is almost certainly overkill, but do we want to use File.join? And I usually prefer Dir.glob over Dir.[] for explicitness.

Dir.glob(File.join(*%w(lib active_interaction locale *.yml)))

This comment has been minimized.

Copy link
@AaronLasseigne

AaronLasseigne Aug 14, 2013

Author Owner

We should definitely use File.join. I could go either way on using glob.

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 14, 2013

Collaborator

It doesn't really make sense to me to index into a class (Dir[...]), but if it's a common Rubyism I'll just accept it.

This comment has been minimized.

Copy link
@AaronLasseigne

AaronLasseigne Aug 14, 2013

Author Owner

I think the idea is kind of like Dir is a list of the files in a directory that's accessible via []. I'm fine with glob.


# @since 0.1.0
module ActiveInteraction end
13 changes: 9 additions & 4 deletions lib/active_interaction/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def persisted?
false
end

def self.i18n_scope
:active_interaction
end

extend OverloadHash

# Returns the output from {#execute} if there are no validation errors or
Expand Down Expand Up @@ -191,12 +195,13 @@ def self.set_up_validator(attribute, type, filter, options, &block)
begin
filter.prepare(attribute, send(attribute), options, &block)
rescue InvalidNestedValue
errors.add(attribute, 'is invalid')
errors.add(attribute, :invalid_nested)
rescue InvalidValue
errors.add(attribute,
"is not a valid #{type.to_s.humanize.downcase}")
errors.add(attribute, :invalid,
type: I18n.translate(:"#{self.class.i18n_scope}.types.#{type.to_s}")

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 13, 2013

Collaborator

Would it make sense to add an i18n_scope instance method?

def i18n_scope
  self.class.i18n_scope
end

This comment has been minimized.

Copy link
@AaronLasseigne

AaronLasseigne Aug 13, 2013

Author Owner

seems like a good idea

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 14, 2013

Collaborator

Also, I don't think the argument to I18n.translate has to be a symbol. A string works just fine.

)
rescue MissingValue
errors.add(attribute, 'is required')
errors.add(attribute, :missing)
end
end
private validator
Expand Down
19 changes: 19 additions & 0 deletions lib/active_interaction/locale/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
en:
active_interaction:
types:
array: 'array'
boolean: 'boolean'
date: 'date'
date_time: 'date_time'

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 13, 2013

Collaborator

Shouldn't this be date_time: 'date time'? It used to be type.to_s.humanize.downcase, which would convert DateTime to date time.

This comment has been minimized.

Copy link
@AaronLasseigne

AaronLasseigne Aug 13, 2013

Author Owner

nice catch

file: 'file'
float: 'float'
hash: 'hash'
integer: 'integer'
model: 'model'
string: 'string'
time: 'time'
errors:
messages:
invalid_nested: 'is invalid'
invalid: 'is not a valid %{type}'
missing: 'is required'
6 changes: 6 additions & 0 deletions spec/active_interaction/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,10 @@ def execute; end
expect(interaction).to_not be_persisted
end
end

describe '.i18n_scope' do
it 'returns the scope' do
expect(described_class.i18n_scope).to eq :active_interaction
end
end
end
161 changes: 161 additions & 0 deletions spec/active_interaction/i18n_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
require 'spec_helper'

describe 'I18n' do
class I18nTest < ActiveInteraction::Base
hash :thing do
integer :i
end

def execute; end
end

context 'types' do
before do
I18n.locale = :en
end

[
:array,
:boolean,
:date,
:date_time,
:file,
:float,
:hash,
:integer,
:model,
:string,
:time
].each do |type|

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 13, 2013

Collaborator

Can we use the %i(...) syntax for this, or was that added in 2.0.0?

This comment has been minimized.

Copy link
@AaronLasseigne

AaronLasseigne Aug 13, 2013

Author Owner

I started to type it that way and then realized that's a 2.0 feature.

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 13, 2013

Collaborator

😢

it "has a value for #{type} in English" do
expect(I18n.translate(:"active_interaction.types.#{type}")).to eq type.to_s
end
end
end

context 'model name' do
let(:model_name) { 'Internationalization Test' }

before do
I18n.locale = :en

I18n.backend.store_translations :en, active_interaction: {
models: {
i18n_test: {
one: model_name,
other: model_name + 's'
}}}
end

it 'returns the translated version of the singular model name' do
expect(I18nTest.model_name.human).to eq model_name
end

it 'returns the translated version of the plural model name' do
expect(I18nTest.model_name.human(count: 2)).to eq model_name + 's'
end
end

context 'attributes' do
let(:attr_name) { 'Thing' }

context 'default' do
it 'returns a humanized version of the attribute name' do
expect(I18nTest.human_attribute_name(:thing)).to eq attr_name
end
end

context 'translated' do
let(:attr_name) { 'Thing'.downcase.reverse.capitalize }

before do
I18n.locale = :reverse

I18n.backend.store_translations :reverse, active_interaction: {
attributes: {
i18n_test: {
thing: attr_name
}}}
end

it 'returns a translated version of the attribute name' do
expect(
I18nTest.human_attribute_name(:thing)
).to eq attr_name
end
end
end

context 'validations' do
context 'default' do
before do
I18n.locale = :en
end

context ':invalid_nested' do
it 'returns "is invalid" in English' do
expect(
I18nTest.run(thing: {i: Object.new}).errors[:thing]
).to eq ['is invalid']
end
end

context ':invalid' do
it 'returns "is not a valid hash" in English' do
expect(
I18nTest.run(thing: Object.new).errors[:thing]
).to eq ['is not a valid hash']
end
end

context ':missing' do
it 'returns "is required" in English' do
expect(
I18nTest.run().errors[:thing]
).to eq ['is required']
end
end
end

context 'translated' do
before do
I18n.locale = :reverse

I18n.backend.store_translations :reverse, active_interaction: {
types: {
hash: 'hash'.reverse
},
errors: {
messages: {
invalid_nested: 'is invalid'.reverse,
invalid: "%{type} #{'is not a valid'.reverse}",
missing: 'is required'.reverse
}}}
end

context ':invalid_nested' do
it 'returns "is invalid" translated' do
expect(
I18nTest.run(thing: {i: Object.new}).errors[:thing]
).to eq ['is invalid'.reverse]
end
end

context ':invalid' do
it 'returns "is not a valid hash" translated' do
expect(
I18nTest.run(thing: Object.new).errors[:thing]
).to eq ['is not a valid hash'.reverse]
end
end

context ':missing' do
it 'returns "is required" translated' do
expect(
I18nTest.run().errors[:thing]
).to eq ['is required'.reverse]
end
end
end
end
end
10 changes: 10 additions & 0 deletions spec/support/interactions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ def execute
defaults_2: defaults_2
}
end

# This causes the tests to fail with:
# Class name cannot be blank. You need to supply a name argument when
# anonymous class given
#
# In particular adding an error in the MissingValue rescue inside
# self.set_up_validator triggers the exception.
def self.model_name
ActiveModel::Name.new(self, nil, 'Temp')
end

This comment has been minimized.

Copy link
@tfausak

tfausak Aug 14, 2013

Collaborator

I ran into all this crap before. You should only define self.name because the method signature for ActiveModel::Name.new changed between version 3.0.9 and 3.1.0. See e333ffa for more details.

end
end

Expand Down

0 comments on commit 906fff7

Please sign in to comment.