1 module I18n
2 module Backend
3 # This module contains several helpers to assist flattening translations.
4 # You may want to flatten translations for:
5 #
6 # 1) speed up lookups, as in the Memoize backend;
7 # 2) In case you want to store translations in a data store, as in ActiveRecord backend;
8 #
9 # You can check both backends above for some examples.
10 # This module also keeps all links in a hash so they can be properly resolved when flattened.
11 module Flatten
12 SEPARATOR_ESCAPE_CHAR = "\001"
13 FLATTEN_SEPARATOR = "."
14
15 # normalize_keys the flatten way. This method is significantly faster
16 # and creates way less objects than the one at I18n.normalize_keys.
17 # It also handles escaping the translation keys.
18 def self.normalize_flat_keys(locale, key, scope, separator)
19 keys = [scope, key].flatten.compact
20 separator ||= I18n.default_separator
21
22 if separator != FLATTEN_SEPARATOR
23 keys.map! do |k|
24 k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
25 "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
26 end
27 end
28
29 keys.join(".")
30 end
31
32 # Receives a string and escape the default separator.
33 def self.escape_default_separator(key) #:nodoc:
34 key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
35 end
36
37 # Shortcut to I18n::Backend::Flatten.normalize_flat_keys
38 # and then resolve_links.
39 def normalize_flat_keys(locale, key, scope, separator)
40 key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
41 resolve_link(locale, key)
42 end
43
44 # Store flattened links.
45 def links
46 @links ||= Hash.new { |h,k| h[k] = {} }
47 end
48
49 # Flatten keys for nested Hashes by chaining up keys:
50 #
51 # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
52 # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
53 #
54 def flatten_keys(hash, escape, prev_key=nil, &block)
55 hash.each_pair do |key, value|
56 key = escape_default_separator(key) if escape
57 curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
58 yield curr_key, value
59 flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
60 end
61 end
62
63 # Receives a hash of translations (where the key is a locale and
64 # the value is another hash) and return a hash with all
65 # translations flattened.
66 #
67 # Nested hashes are included in the flattened hash just if subtree
68 # is true and Symbols are automatically stored as links.
69 def flatten_translations(locale, data, escape, subtree)
70 hash = {}
71 flatten_keys(data, escape) do |key, value|
72 if value.is_a?(Hash)
73 hash[key] = value if subtree
74 else
75 store_link(locale, key, value) if value.is_a?(Symbol)
76 hash[key] = value
77 end
78 end
79 hash
80 end
81
82 protected
83
84 def store_link(locale, key, link)
85 links[locale.to_sym][key.to_s] = link.to_s
86 end
87
88 def resolve_link(locale, key)
89 key, locale = key.to_s, locale.to_sym
90 links = self.links[locale]
91
92 if links.key?(key)
93 links[key]
94 elsif link = find_link(locale, key)
95 store_link(locale, key, key.gsub(*link))
96 else
97 key
98 end
99 end
100
101 def find_link(locale, key) #:nodoc:
102 links[locale].each do |from, to|
103 return [from, to] if key[0, from.length] == from
104 end && nil
105 end
106
107 def escape_default_separator(key) #:nodoc:
108 I18n::Backend::Flatten.escape_default_separator(key)
109 end
110
111 end
112 end
113 en