globalize2 0.1.0
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/.gitignore +4 -0
- data/LICENSE +21 -0
- data/README.textile +202 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/generators/db_backend.rb +0 -0
- data/generators/templates/db_backend_migration.rb +25 -0
- data/globalize2.gemspec +100 -0
- data/init.rb +8 -0
- data/lib/globalize/backend/chain.rb +102 -0
- data/lib/globalize/backend/pluralizing.rb +37 -0
- data/lib/globalize/backend/static.rb +61 -0
- data/lib/globalize/i18n/missing_translations_log_handler.rb +41 -0
- data/lib/globalize/i18n/missing_translations_raise_handler.rb +27 -0
- data/lib/globalize/load_path.rb +63 -0
- data/lib/globalize/locale/fallbacks.rb +63 -0
- data/lib/globalize/locale/language_tag.rb +81 -0
- data/lib/globalize/model/active_record.rb +56 -0
- data/lib/globalize/model/active_record/adapter.rb +100 -0
- data/lib/globalize/model/active_record/translated.rb +174 -0
- data/lib/globalize/translation.rb +32 -0
- data/lib/locale/root.yml +3 -0
- data/lib/rails_edge_load_path_patch.rb +40 -0
- data/notes.textile +51 -0
- data/test/all.rb +2 -0
- data/test/backends/chained_test.rb +175 -0
- data/test/backends/pluralizing_test.rb +63 -0
- data/test/backends/static_test.rb +147 -0
- data/test/data/locale/all.yml +2 -0
- data/test/data/locale/de-DE.yml +2 -0
- data/test/data/locale/en-US.yml +2 -0
- data/test/data/locale/en-US/module.yml +2 -0
- data/test/data/locale/fi-FI/module.yml +2 -0
- data/test/data/locale/root.yml +0 -0
- data/test/data/models.rb +40 -0
- data/test/data/no_globalize_schema.rb +11 -0
- data/test/data/schema.rb +39 -0
- data/test/i18n/missing_translations_test.rb +36 -0
- data/test/load_path_test.rb +49 -0
- data/test/locale/fallbacks_test.rb +154 -0
- data/test/locale/language_tag_test.rb +130 -0
- data/test/model/active_record/migration_test.rb +123 -0
- data/test/model/active_record/sti_translated_test.rb +75 -0
- data/test/model/active_record/translated_test.rb +487 -0
- data/test/test_helper.rb +36 -0
- data/test/translation_test.rb +54 -0
- metadata +116 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'i18n/backend/simple'
|
2
|
+
|
3
|
+
module Globalize
|
4
|
+
module Backend
|
5
|
+
class Pluralizing < I18n::Backend::Simple
|
6
|
+
def pluralize(locale, entry, count)
|
7
|
+
return entry unless entry.is_a?(Hash) and count
|
8
|
+
key = :zero if count == 0 && entry.has_key?(:zero)
|
9
|
+
key ||= pluralizer(locale).call(count)
|
10
|
+
raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
|
11
|
+
translation entry[key], :plural_key => key
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_pluralizer(locale, pluralizer)
|
15
|
+
pluralizers[locale.to_sym] = pluralizer
|
16
|
+
end
|
17
|
+
|
18
|
+
def pluralizer(locale)
|
19
|
+
pluralizers[locale.to_sym] || default_pluralizer
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def default_pluralizer
|
24
|
+
pluralizers[:en]
|
25
|
+
end
|
26
|
+
|
27
|
+
def pluralizers
|
28
|
+
@pluralizers ||= { :en => lambda{|n| n == 1 ? :one : :other } }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Overwrite this method to return something other than a String
|
32
|
+
def translation(string, attributes)
|
33
|
+
string
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'globalize/backend/pluralizing'
|
2
|
+
require 'globalize/locale/fallbacks'
|
3
|
+
require 'globalize/translation'
|
4
|
+
|
5
|
+
module Globalize
|
6
|
+
module Backend
|
7
|
+
class Static < Pluralizing
|
8
|
+
def initialize(*args)
|
9
|
+
add(*args) unless args.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def translate(locale, key, options = {})
|
13
|
+
result, default, fallback = nil, options.delete(:default), nil
|
14
|
+
I18n.fallbacks[locale].each do |fallback|
|
15
|
+
begin
|
16
|
+
result = super(fallback, key, options) and break
|
17
|
+
rescue I18n::MissingTranslationData
|
18
|
+
end
|
19
|
+
end
|
20
|
+
result ||= default locale, default, options
|
21
|
+
|
22
|
+
attrs = {:requested_locale => locale, :locale => fallback, :key => key, :options => options}
|
23
|
+
translation(result, attrs)
|
24
|
+
# translation(result, attrs) || raise(I18n::MissingTranslationData.new(locale, key, options))
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
alias :orig_interpolate :interpolate unless method_defined? :orig_interpolate
|
30
|
+
def interpolate(locale, string, values = {})
|
31
|
+
result = orig_interpolate(locale, string, values)
|
32
|
+
translation = translation(string)
|
33
|
+
translation.nil? ? result : translation.replace(result)
|
34
|
+
end
|
35
|
+
|
36
|
+
def translation(result, meta = nil)
|
37
|
+
return unless result
|
38
|
+
|
39
|
+
case result
|
40
|
+
when Numeric
|
41
|
+
result
|
42
|
+
when String
|
43
|
+
result = Translation::Static.new(result) unless result.is_a? Translation::Static
|
44
|
+
result.set_meta meta
|
45
|
+
result
|
46
|
+
when Hash
|
47
|
+
Hash[*result.map do |key, value|
|
48
|
+
[key, translation(value, meta)]
|
49
|
+
end.flatten]
|
50
|
+
when Array
|
51
|
+
result.map do |value|
|
52
|
+
translation(value, meta)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
result
|
56
|
+
# raise "unexpected translation type: #{result.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# A simple exception handler that behaves like the default exception handler
|
2
|
+
# but additionally logs missing translations to a given log.
|
3
|
+
#
|
4
|
+
# Useful for identifying missing translations during testing.
|
5
|
+
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_log_handler
|
9
|
+
# I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
|
10
|
+
# I18n.exception_handler = :missing_translations_log_handler
|
11
|
+
#
|
12
|
+
# To set up a different log file:
|
13
|
+
#
|
14
|
+
# logger = Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
|
15
|
+
# I18n.missing_translations_logger = logger
|
16
|
+
|
17
|
+
module I18n
|
18
|
+
@@missing_translations_logger = nil
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def missing_translations_logger
|
22
|
+
@@missing_translations_logger ||= begin
|
23
|
+
require 'logger' unless defined?(Logger)
|
24
|
+
Logger.new(STDOUT)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def missing_translations_logger=(logger)
|
29
|
+
@@missing_translations_logger = logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def missing_translations_log_handler(exception, locale, key, options)
|
33
|
+
if MissingTranslationData === exception
|
34
|
+
missing_translations_logger.warn(exception.message)
|
35
|
+
return exception.message
|
36
|
+
else
|
37
|
+
raise exception
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# A simple exception handler that behaves like the default exception handler
|
2
|
+
# but also raises on missing translations.
|
3
|
+
#
|
4
|
+
# Useful for identifying missing translations during testing.
|
5
|
+
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_raise_handler
|
9
|
+
# I18n.exception_handler = :missing_translations_raise_handler
|
10
|
+
module I18n
|
11
|
+
class << self
|
12
|
+
def missing_translations_raise_handler(exception, locale, key, options)
|
13
|
+
raise exception
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# self.exception_handler = :missing_translations_raise_handler
|
18
|
+
end
|
19
|
+
|
20
|
+
I18n.exception_handler = :missing_translations_raise_handler
|
21
|
+
|
22
|
+
ActionView::Helpers::TranslationHelper.module_eval do
|
23
|
+
def translate(key, options = {})
|
24
|
+
I18n.translate(key, options)
|
25
|
+
end
|
26
|
+
alias :t :translate
|
27
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Locale load_path and Locale loading support.
|
2
|
+
#
|
3
|
+
# To use this include the Globalize::LoadPath::I18n module to I18n like this:
|
4
|
+
#
|
5
|
+
# I18n.send :include, Globalize::LoadPath::I18n
|
6
|
+
#
|
7
|
+
# Clients can add load_paths using:
|
8
|
+
#
|
9
|
+
# I18n.load_path.add load_path, 'rb', 'yml' # pass any number of extensions like this
|
10
|
+
# I18n.load_path << 'path/to/dir' # usage without an extension, defaults to 'yml'
|
11
|
+
#
|
12
|
+
# And load locale data using either of:
|
13
|
+
#
|
14
|
+
# I18n.load_locales 'en-US', 'de-DE'
|
15
|
+
# I18n.load_locale 'en-US'
|
16
|
+
#
|
17
|
+
# This will lookup all files named like:
|
18
|
+
#
|
19
|
+
# 'path/to/dir/all.yml'
|
20
|
+
# 'path/to/dir/en-US.yml'
|
21
|
+
# 'path/to/dir/en-US/*.yml'
|
22
|
+
#
|
23
|
+
# The filenames will be passed to I18n.load_translations which delegates to
|
24
|
+
# the backend. So the actual behaviour depends on the implementation of the
|
25
|
+
# backend. I18n::Backend::Simple will be able to read YAML and plain Ruby
|
26
|
+
# files. See the documentation for I18n.load_translations for details.
|
27
|
+
|
28
|
+
module Globalize
|
29
|
+
class LoadPath < Array
|
30
|
+
def extensions
|
31
|
+
@extensions ||= ['rb', 'yml']
|
32
|
+
end
|
33
|
+
attr_writer :extensions
|
34
|
+
|
35
|
+
def locales
|
36
|
+
@locales ||= ['*']
|
37
|
+
end
|
38
|
+
attr_writer :locales
|
39
|
+
|
40
|
+
def <<(path)
|
41
|
+
push path
|
42
|
+
end
|
43
|
+
|
44
|
+
def push(*paths)
|
45
|
+
super(*paths.map{|path| filenames(path) }.flatten.uniq.sort)
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def filenames(path)
|
51
|
+
return [path] if File.file? path
|
52
|
+
patterns(path).map{|pattern| Dir[pattern] }
|
53
|
+
end
|
54
|
+
|
55
|
+
def patterns(path)
|
56
|
+
locales.map do |locale|
|
57
|
+
extensions.map do |extension|
|
58
|
+
%W(#{path}/all.#{extension} #{path}/#{locale}.#{extension} #{path}/#{locale}/**/*.#{extension})
|
59
|
+
end
|
60
|
+
end.flatten.uniq
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'globalize/locale/language_tag'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
@@fallbacks = nil
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Returns the current fallbacks. Defaults to +Globalize::Locale::Fallbacks+.
|
8
|
+
def fallbacks
|
9
|
+
@@fallbacks ||= Globalize::Locale::Fallbacks.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets the current fallbacks. Used to set a custom fallbacks instance.
|
13
|
+
def fallbacks=(fallbacks)
|
14
|
+
@@fallbacks = fallbacks
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Globalize
|
20
|
+
module Locale
|
21
|
+
class Fallbacks < Hash
|
22
|
+
def initialize(*defaults)
|
23
|
+
@map = {}
|
24
|
+
map defaults.pop if defaults.last.is_a?(Hash)
|
25
|
+
|
26
|
+
defaults = [I18n.default_locale.to_sym] if defaults.empty?
|
27
|
+
self.defaults = defaults
|
28
|
+
end
|
29
|
+
|
30
|
+
def defaults=(defaults)
|
31
|
+
@defaults = defaults.map{|default| compute(default, false) }.flatten << :root
|
32
|
+
end
|
33
|
+
attr_reader :defaults
|
34
|
+
|
35
|
+
def [](tag)
|
36
|
+
tag = tag.to_sym
|
37
|
+
has_key?(tag) ? fetch(tag) : store(tag, compute(tag))
|
38
|
+
end
|
39
|
+
|
40
|
+
def map(mappings)
|
41
|
+
mappings.each do |from, to|
|
42
|
+
from, to = from.to_sym, Array(to)
|
43
|
+
to.each do |to|
|
44
|
+
@map[from] ||= []
|
45
|
+
@map[from] << to.to_sym
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def compute(tags, include_defaults = true)
|
53
|
+
result = Array(tags).collect do |tag|
|
54
|
+
tags = LanguageTag::tag(tag.to_sym).parents(true).map! {|t| t.to_sym }
|
55
|
+
tags.each{|tag| tags += compute(@map[tag]) if @map[tag] }
|
56
|
+
tags
|
57
|
+
end.flatten
|
58
|
+
result.push *defaults if include_defaults
|
59
|
+
result.uniq
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# for specifications see http://en.wikipedia.org/wiki/IETF_language_tag
|
2
|
+
#
|
3
|
+
# SimpleParser does not implement advanced usages such as grandfathered tags
|
4
|
+
|
5
|
+
module Globalize
|
6
|
+
module Locale
|
7
|
+
module Rfc4646
|
8
|
+
SUBTAGS = [:language, :script, :region, :variant, :extension, :privateuse, :grandfathered]
|
9
|
+
FORMATS = {:language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase}
|
10
|
+
end
|
11
|
+
|
12
|
+
class LanguageTag < Struct.new(*Rfc4646::SUBTAGS)
|
13
|
+
class << self
|
14
|
+
def parser
|
15
|
+
@@parser ||= SimpleParser
|
16
|
+
end
|
17
|
+
|
18
|
+
def parser=(parser)
|
19
|
+
@@parser = parser
|
20
|
+
end
|
21
|
+
|
22
|
+
def tag(tag)
|
23
|
+
matches = parser.match(tag)
|
24
|
+
new *matches if matches
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Rfc4646::FORMATS.each do |name, format|
|
29
|
+
define_method(name) { self[name].send(format) unless self[name].nil? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sym
|
33
|
+
to_s.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@tag ||= to_a.compact.join("-")
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
members.collect {|attr| self.send(attr) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def parent
|
45
|
+
segs = to_a.compact
|
46
|
+
segs.length < 2 ? nil : LanguageTag.tag(segs[0..(segs.length-2)].join('-'))
|
47
|
+
end
|
48
|
+
|
49
|
+
def parents(include_self = true)
|
50
|
+
result, parent = [], self.dup
|
51
|
+
result << parent if include_self
|
52
|
+
while parent = parent.parent
|
53
|
+
result << parent
|
54
|
+
end
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
module SimpleParser
|
59
|
+
PATTERN = %r{\A(?:
|
60
|
+
([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language
|
61
|
+
(?:-([a-z]{4}))? # script
|
62
|
+
(?:-([a-z]{2}|\d{3}))? # region
|
63
|
+
(?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant
|
64
|
+
(?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension
|
65
|
+
(?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag
|
66
|
+
(x(?:-[0-9a-z]{1,8})+)| # privateuse tag
|
67
|
+
/* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered
|
68
|
+
)\z}xi
|
69
|
+
|
70
|
+
class << self
|
71
|
+
def match(tag)
|
72
|
+
c = PATTERN.match(tag.to_s).captures
|
73
|
+
c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here?
|
74
|
+
rescue
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'globalize/translation'
|
2
|
+
require 'globalize/locale/fallbacks'
|
3
|
+
require 'globalize/model/active_record/adapter'
|
4
|
+
require 'globalize/model/active_record/translated'
|
5
|
+
|
6
|
+
module Globalize
|
7
|
+
module Model
|
8
|
+
module ActiveRecord
|
9
|
+
class << self
|
10
|
+
def create_proxy_class(klass)
|
11
|
+
module_names = klass.name.split('::')
|
12
|
+
klass_name = module_names.pop
|
13
|
+
target = module_names.empty? ? Object : module_names.join('::').constantize
|
14
|
+
|
15
|
+
proxy_class_name = "#{klass_name}Translation"
|
16
|
+
proxy_class = nil
|
17
|
+
begin
|
18
|
+
proxy_class = proxy_class_name.constantize
|
19
|
+
rescue NameError
|
20
|
+
proxy_class = target.const_set proxy_class_name, Class.new(::ActiveRecord::Base)
|
21
|
+
end
|
22
|
+
|
23
|
+
proxy_class.instance_eval do
|
24
|
+
belongs_to "#{klass.name.underscore.gsub('/', '_')}".intern
|
25
|
+
end
|
26
|
+
proxy_class.class_eval do
|
27
|
+
def locale
|
28
|
+
read_attribute(:locale).to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
def locale=(locale)
|
32
|
+
write_attribute(:locale, locale.to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return proxy_class
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_accessors(klass, attr_names)
|
40
|
+
attr_names.each do |attr_name|
|
41
|
+
klass.send :define_method, attr_name, lambda {
|
42
|
+
globalize.fetch self.class.locale, attr_name
|
43
|
+
}
|
44
|
+
klass.send :define_method, "#{attr_name}_before_type_cast", lambda {
|
45
|
+
globalize.fetch self.class.locale, attr_name
|
46
|
+
}
|
47
|
+
klass.send :define_method, "#{attr_name}=", lambda {|val|
|
48
|
+
globalize.stash self.class.locale, attr_name, val
|
49
|
+
self[attr_name] = val
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Globalize
|
2
|
+
module Model
|
3
|
+
class AttributeStash < Hash
|
4
|
+
def contains?(locale, attr_name)
|
5
|
+
locale = locale.to_sym
|
6
|
+
self[locale] ||= {}
|
7
|
+
self[locale].has_key? attr_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def read(locale, attr_name)
|
11
|
+
locale = locale.to_sym
|
12
|
+
self[locale] ||= {}
|
13
|
+
self[locale][attr_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(locale, attr_name, value)
|
17
|
+
locale = locale.to_sym
|
18
|
+
self[locale] ||= {}
|
19
|
+
self[locale][attr_name] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Adapter
|
24
|
+
def initialize(record)
|
25
|
+
@record = record
|
26
|
+
|
27
|
+
# TODO what exactly are the roles of cache and stash
|
28
|
+
@cache = AttributeStash.new
|
29
|
+
@stash = AttributeStash.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch(locale, attr_name)
|
33
|
+
# locale = I18n.locale
|
34
|
+
is_cached = @cache.contains?(locale, attr_name)
|
35
|
+
is_cached ? @cache.read(locale, attr_name) : begin
|
36
|
+
value = fetch_attribute locale, attr_name
|
37
|
+
@cache.write locale, attr_name, value if value && value.locale == locale
|
38
|
+
value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def stash(locale, attr_name, value)
|
43
|
+
@stash.write locale, attr_name, value
|
44
|
+
@cache.write locale, attr_name, value
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_translations!
|
48
|
+
@stash.each do |locale, attrs|
|
49
|
+
translation = @record.globalize_translations.find_or_initialize_by_locale(locale.to_s)
|
50
|
+
attrs.each{|attr_name, value| translation[attr_name] = value }
|
51
|
+
translation.save!
|
52
|
+
end
|
53
|
+
@stash.clear
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clears the cache
|
57
|
+
def clear
|
58
|
+
@cache.clear
|
59
|
+
@stash.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_cache
|
63
|
+
@cache.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def fetch_attribute(locale, attr_name)
|
69
|
+
fallbacks = I18n.fallbacks[locale].map{|tag| tag.to_s}.map(&:to_sym)
|
70
|
+
|
71
|
+
# If the translations were included with
|
72
|
+
# :include => globalize_translations
|
73
|
+
# there is no need to query them again.
|
74
|
+
unless @record.globalize_translations.loaded?
|
75
|
+
translations = @record.globalize_translations.by_locales(fallbacks)
|
76
|
+
else
|
77
|
+
translations = @record.globalize_translations
|
78
|
+
end
|
79
|
+
result, requested_locale = nil, locale
|
80
|
+
|
81
|
+
# Walk through the fallbacks, starting with the current locale itself, and moving
|
82
|
+
# to the next best choice, until we find a match.
|
83
|
+
# Check the @globalize_set_translations cache first to see if we've just changed the
|
84
|
+
# attribute and not saved yet.
|
85
|
+
fallbacks.each do |fallback|
|
86
|
+
# TODO should we be checking stash or just cache?
|
87
|
+
result = @cache.read(fallback, attr_name) || begin
|
88
|
+
translation = translations.detect {|tr| tr.locale == fallback }
|
89
|
+
translation && translation.send(attr_name)
|
90
|
+
end
|
91
|
+
if result
|
92
|
+
locale = fallback
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
result && Translation::Attribute.new(result, :locale => locale, :requested_locale => requested_locale)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|