traco 0.3.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +31 -13
- data/lib/traco/class_methods.rb +24 -14
- data/lib/traco/localized_reader.rb +6 -6
- data/lib/traco/translates.rb +35 -26
- data/lib/traco/version.rb +1 -1
- data/spec/traco_spec.rb +29 -6
- metadata +16 -16
data/README.markdown
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](http://travis-ci.org/barsoom/traco)
|
4
4
|
|
5
|
-
Translatable
|
5
|
+
Translatable attributes for Rails 3, stored in the model table itself.
|
6
6
|
|
7
7
|
Inspired by Iain Hecker's [translatable_columns](https://github.com/iain/translatable_columns/).
|
8
8
|
|
@@ -26,9 +26,9 @@ Write a migration to get database columns with locale suffixes, e.g. `title_sv`
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
Don't create a column named `title` without a suffix, since Traco will define a method with that name.
|
29
|
+
Don't create a database column named `title` without a suffix, since Traco will define a method with that name.
|
30
30
|
|
31
|
-
Declare
|
31
|
+
Declare the attributes in the model:
|
32
32
|
|
33
33
|
class Post < ActiveRecord::Base
|
34
34
|
translates :title, :body
|
@@ -42,17 +42,26 @@ You can still use your accessors like `title_sv` and `title_sv=` in forms, valid
|
|
42
42
|
|
43
43
|
`.human_attribute_name(:title_sv)`: Extends this standard method to return "Title (Swedish)" if you have a translation key `i18n.languages.sv = "Swedish"` and "Title (SV)" otherwise. Rails uses this method to build validation error messages and form labels.
|
44
44
|
|
45
|
-
`.
|
45
|
+
`.translatable_attributes`: Returns an array like `[:title, :body]`.
|
46
46
|
|
47
|
-
`.
|
47
|
+
`.locale_columns(:title)`: Returns an array like `[:title_sv, :title_en]` sorted with default locale first and then alphabetically. Suitable for looping in forms:
|
48
48
|
|
49
|
-
<% Post.
|
49
|
+
<% Post.locale_columns(:title).each do |column| %>
|
50
50
|
<p>
|
51
|
-
<%= form.label
|
52
|
-
<%= form.text_field
|
51
|
+
<%= form.label column %>
|
52
|
+
<%= form.text_field column %>
|
53
53
|
</p>
|
54
54
|
<% end %>
|
55
55
|
|
56
|
+
Or perhaps for things like:
|
57
|
+
|
58
|
+
attr_accessible *locale_columns(:name)
|
59
|
+
|
60
|
+
validates *locale_columns(:name),
|
61
|
+
:uniqueness => true
|
62
|
+
|
63
|
+
`.locales_for_attribute(:title)`: Returns an array like `[:sv, :en]` sorted with default locale first and then alphabetically.
|
64
|
+
|
56
65
|
And the equivalent methods for `body`, of course.
|
57
66
|
|
58
67
|
|
@@ -68,15 +77,24 @@ if you specify
|
|
68
77
|
then `#title` will return `nil` if there is no translation in the current locale, instead of falling back to the default locale.
|
69
78
|
|
70
79
|
|
71
|
-
|
80
|
+
### Overriding methods
|
72
81
|
|
73
|
-
|
82
|
+
Methods are defined in an included module, so you can just override them and call Traco's implementation with `super`:
|
83
|
+
|
84
|
+
class Post < ActiveRecord::Base
|
85
|
+
translates :title
|
86
|
+
|
87
|
+
def title
|
88
|
+
super.reverse
|
89
|
+
end
|
90
|
+
end
|
74
91
|
|
75
|
-
gem 'traco', github: 'barsoom/traco'
|
76
92
|
|
77
|
-
|
93
|
+
## Installation
|
94
|
+
|
95
|
+
Add this to your `Gemfile` if you use Bundler 1.1+:
|
78
96
|
|
79
|
-
gem 'traco'
|
97
|
+
gem 'traco'
|
80
98
|
|
81
99
|
Then run
|
82
100
|
|
data/lib/traco/class_methods.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
module Traco
|
2
2
|
module ClassMethods
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
def locales_for_attribute(attribute)
|
4
|
+
re = /\A#{attribute}_([a-z]{2})\z/
|
5
|
+
|
6
|
+
column_names.
|
7
|
+
grep(re) { $1.to_sym }.
|
8
|
+
sort_by(&locale_sort_value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def locale_columns(attribute)
|
12
|
+
locales_for_attribute(attribute).map { |locale|
|
13
|
+
:"#{attribute}_#{locale}"
|
14
|
+
}
|
7
15
|
end
|
8
16
|
|
9
17
|
def human_attribute_name(attribute, options = {})
|
@@ -19,18 +27,20 @@ module Traco
|
|
19
27
|
|
20
28
|
private
|
21
29
|
|
22
|
-
def locale_sort_value
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
def locale_sort_value
|
31
|
+
lambda { |locale|
|
32
|
+
if locale == I18n.default_locale
|
33
|
+
# Sort the default locale first.
|
34
|
+
"0"
|
35
|
+
else
|
36
|
+
# Sort the rest alphabetically.
|
37
|
+
locale.to_s
|
38
|
+
end
|
39
|
+
}
|
30
40
|
end
|
31
41
|
|
32
|
-
def translates?(
|
33
|
-
|
42
|
+
def translates?(attribute)
|
43
|
+
translatable_attributes.include?(attribute.to_sym)
|
34
44
|
end
|
35
45
|
|
36
46
|
def locale_name(locale)
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Traco
|
2
2
|
class LocalizedReader
|
3
|
-
def initialize(record,
|
3
|
+
def initialize(record, attribute, options)
|
4
4
|
@record = record
|
5
|
-
@
|
5
|
+
@attribute = attribute
|
6
6
|
@fallback = options[:fallback]
|
7
7
|
end
|
8
8
|
|
9
9
|
def value
|
10
10
|
locales_to_try.each do |locale|
|
11
|
-
value = @record.send("#{@
|
11
|
+
value = @record.send("#{@attribute}_#{locale}")
|
12
12
|
return value if value.present?
|
13
13
|
end
|
14
14
|
|
@@ -18,7 +18,7 @@ module Traco
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def locales_to_try
|
21
|
-
@locales_to_try ||= locale_chain &
|
21
|
+
@locales_to_try ||= locale_chain & locales_for_attribute
|
22
22
|
end
|
23
23
|
|
24
24
|
def locale_chain
|
@@ -28,8 +28,8 @@ module Traco
|
|
28
28
|
chain
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
@record.class.
|
31
|
+
def locales_for_attribute
|
32
|
+
@record.class.locales_for_attribute(@attribute)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/traco/translates.rb
CHANGED
@@ -1,52 +1,61 @@
|
|
1
1
|
module Traco
|
2
2
|
module Translates
|
3
|
-
def translates(*
|
4
|
-
options =
|
3
|
+
def translates(*attributes)
|
4
|
+
options = attributes.extract_options!
|
5
5
|
set_up_once
|
6
|
-
|
6
|
+
store_as_translatable_attributes attributes.map(&:to_sym), options
|
7
7
|
end
|
8
8
|
|
9
9
|
private
|
10
10
|
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# Only called once per class or inheritance chain (e.g. once
|
12
|
+
# for the superclass, not at all for subclasses). The separation
|
13
|
+
# is important if we don't want to overwrite values if running
|
14
|
+
# multiple times in the same class or in different classes of
|
15
|
+
# an inheritance chain.
|
13
16
|
def set_up_once
|
14
|
-
return if respond_to?(:
|
17
|
+
return if respond_to?(:translatable_attributes)
|
15
18
|
|
16
|
-
|
19
|
+
class_attribute :traco_instance_methods
|
20
|
+
class_attribute :translatable_attributes
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
self.translatable_attributes = []
|
23
|
+
extend Traco::ClassMethods
|
20
24
|
end
|
21
25
|
|
22
|
-
def
|
26
|
+
def store_as_translatable_attributes(attributes, options)
|
23
27
|
fallback = options.fetch(:fallback, true)
|
24
28
|
|
25
|
-
self.
|
29
|
+
self.translatable_attributes |= attributes
|
30
|
+
|
31
|
+
# Instance methods are defined on an included module, so your class
|
32
|
+
# can just redefine them and call `super`, if you need to.
|
33
|
+
self.traco_instance_methods = Module.new
|
34
|
+
include traco_instance_methods
|
26
35
|
|
27
|
-
|
28
|
-
define_localized_reader
|
29
|
-
define_localized_writer
|
36
|
+
attributes.each do |attribute|
|
37
|
+
define_localized_reader attribute, :fallback => fallback
|
38
|
+
define_localized_writer attribute
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
33
|
-
def define_localized_reader(
|
42
|
+
def define_localized_reader(attribute, options)
|
34
43
|
fallback = options[:fallback]
|
35
44
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
self,
|
40
|
-
|
41
|
-
|
42
|
-
)
|
43
|
-
@localized_readers[column].value
|
45
|
+
traco_instance_methods.module_eval do
|
46
|
+
define_method(attribute) do
|
47
|
+
@localized_readers ||= {}
|
48
|
+
@localized_readers[attribute] ||= Traco::LocalizedReader.new(self, attribute, :fallback => fallback)
|
49
|
+
@localized_readers[attribute].value
|
50
|
+
end
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
47
|
-
def define_localized_writer(
|
48
|
-
|
49
|
-
|
54
|
+
def define_localized_writer(attribute)
|
55
|
+
traco_instance_methods.module_eval do
|
56
|
+
define_method("#{attribute}=") do |value|
|
57
|
+
send("#{attribute}_#{I18n.locale}=", value)
|
58
|
+
end
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
data/lib/traco/version.rb
CHANGED
data/spec/traco_spec.rb
CHANGED
@@ -28,29 +28,42 @@ describe ActiveRecord::Base, ".translates" do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
describe Post, ".
|
31
|
+
describe Post, ".translatable_attributes" do
|
32
32
|
before do
|
33
33
|
Post.translates :title
|
34
34
|
end
|
35
35
|
|
36
|
-
it "should list the translatable
|
37
|
-
Post.
|
36
|
+
it "should list the translatable attributes" do
|
37
|
+
Post.translatable_attributes.should == [ :title ]
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
describe Post, ".
|
41
|
+
describe Post, ".locales_for_attribute" do
|
42
42
|
before do
|
43
43
|
Post.translates :title
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should list the locales, default first and then alphabetically" do
|
47
47
|
I18n.default_locale = :fi
|
48
|
-
Post.
|
48
|
+
Post.locales_for_attribute(:title).should == [
|
49
49
|
:fi, :en, :sv
|
50
50
|
]
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
describe Post, ".locale_columns" do
|
55
|
+
before do
|
56
|
+
Post.translates :title
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should list the columns-with-locale for that attribute, default locale first and then alphabetically" do
|
60
|
+
I18n.default_locale = :fi
|
61
|
+
Post.locale_columns(:title).should == [
|
62
|
+
:title_fi, :title_en, :title_sv
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
54
67
|
describe Post, "#title" do
|
55
68
|
let(:post) {
|
56
69
|
Post.new(:title_sv => "Hej", :title_en => "Halloa", :title_fi => "Moi moi")
|
@@ -89,6 +102,16 @@ describe Post, "#title" do
|
|
89
102
|
post.title.should be_nil
|
90
103
|
end
|
91
104
|
|
105
|
+
it "should be overridable" do
|
106
|
+
class Post
|
107
|
+
def title
|
108
|
+
super.reverse
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
post.title.should == "jeH"
|
113
|
+
end
|
114
|
+
|
92
115
|
# Had a regression.
|
93
116
|
it "handles multiple columns" do
|
94
117
|
Post.translates :title, :body
|
@@ -104,7 +127,7 @@ describe Post, "#title" do
|
|
104
127
|
}
|
105
128
|
|
106
129
|
before do
|
107
|
-
Post.translates :title, fallback
|
130
|
+
Post.translates :title, :fallback => false
|
108
131
|
I18n.default_locale = :en
|
109
132
|
end
|
110
133
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: traco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &70327471440700 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70327471440700
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sqlite3
|
27
|
-
requirement: &
|
27
|
+
requirement: &70327471440280 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70327471440280
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &70327471423380 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70327471423380
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70327471422960 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70327471422960
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: guard
|
60
|
-
requirement: &
|
60
|
+
requirement: &70327471422540 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70327471422540
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: guard-rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70327471422120 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70327471422120
|
80
80
|
description:
|
81
81
|
email:
|
82
82
|
- henrik@barsoom.se
|
@@ -115,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
115
|
version: '0'
|
116
116
|
segments:
|
117
117
|
- 0
|
118
|
-
hash:
|
118
|
+
hash: -237714647746195328
|
119
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
120
|
none: false
|
121
121
|
requirements:
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
124
|
version: '0'
|
125
125
|
segments:
|
126
126
|
- 0
|
127
|
-
hash:
|
127
|
+
hash: -237714647746195328
|
128
128
|
requirements: []
|
129
129
|
rubyforge_project:
|
130
130
|
rubygems_version: 1.8.5
|