i18n-tasks 0.6.3 → 0.7.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +2 -1
  4. data/README.md +80 -78
  5. data/bin/i18n-tasks +24 -30
  6. data/config/i18n-tasks.yml +87 -0
  7. data/config/locales/en.yml +95 -0
  8. data/i18n-tasks.gemspec +1 -0
  9. data/lib/i18n/tasks.rb +10 -0
  10. data/lib/i18n/tasks/base_task.rb +6 -2
  11. data/lib/i18n/tasks/command/collection.rb +18 -0
  12. data/lib/i18n/tasks/command/commander.rb +72 -0
  13. data/lib/i18n/tasks/command/commands/data.rb +73 -0
  14. data/lib/i18n/tasks/command/commands/eq_base.rb +20 -0
  15. data/lib/i18n/tasks/command/commands/health.rb +26 -0
  16. data/lib/i18n/tasks/command/commands/meta.rb +35 -0
  17. data/lib/i18n/tasks/command/commands/missing.rb +73 -0
  18. data/lib/i18n/tasks/command/commands/tree.rb +92 -0
  19. data/lib/i18n/tasks/command/commands/usages.rb +70 -0
  20. data/lib/i18n/tasks/command/commands/xlsx.rb +27 -0
  21. data/lib/i18n/tasks/command/dsl.rb +27 -0
  22. data/lib/i18n/tasks/command/dsl/cmd.rb +19 -0
  23. data/lib/i18n/tasks/command/dsl/cmd_opt.rb +19 -0
  24. data/lib/i18n/tasks/command/dsl/enum_opt.rb +26 -0
  25. data/lib/i18n/tasks/command/options/common.rb +48 -0
  26. data/lib/i18n/tasks/command/options/enum_opt.rb +44 -0
  27. data/lib/i18n/tasks/command/options/list_opt.rb +11 -0
  28. data/lib/i18n/tasks/command/options/locales.rb +47 -0
  29. data/lib/i18n/tasks/command/options/trees.rb +101 -0
  30. data/lib/i18n/tasks/command_error.rb +3 -0
  31. data/lib/i18n/tasks/commands.rb +22 -169
  32. data/lib/i18n/tasks/configuration.rb +1 -16
  33. data/lib/i18n/tasks/console_context.rb +1 -1
  34. data/lib/i18n/tasks/data.rb +13 -8
  35. data/lib/i18n/tasks/data/file_formats.rb +29 -18
  36. data/lib/i18n/tasks/data/file_system_base.rb +35 -4
  37. data/lib/i18n/tasks/data/router/conservative_router.rb +18 -11
  38. data/lib/i18n/tasks/data/tree/node.rb +5 -15
  39. data/lib/i18n/tasks/data/tree/nodes.rb +0 -3
  40. data/lib/i18n/tasks/data/tree/siblings.rb +32 -2
  41. data/lib/i18n/tasks/data/tree/traversal.rb +117 -96
  42. data/lib/i18n/tasks/google_translation.rb +25 -25
  43. data/lib/i18n/tasks/html_keys.rb +10 -0
  44. data/lib/i18n/tasks/key_pattern_matching.rb +1 -0
  45. data/lib/i18n/tasks/locale_list.rb +19 -0
  46. data/lib/i18n/tasks/missing_keys.rb +32 -33
  47. data/lib/i18n/tasks/plural_keys.rb +1 -1
  48. data/lib/i18n/tasks/reports/base.rb +4 -9
  49. data/lib/i18n/tasks/reports/spreadsheet.rb +5 -5
  50. data/lib/i18n/tasks/reports/terminal.rb +62 -38
  51. data/lib/i18n/tasks/scanners/base_scanner.rb +5 -4
  52. data/lib/i18n/tasks/slop_command.rb +27 -0
  53. data/lib/i18n/tasks/stats.rb +20 -0
  54. data/lib/i18n/tasks/string_interpolation.rb +14 -0
  55. data/lib/i18n/tasks/unused_keys.rb +0 -10
  56. data/lib/i18n/tasks/version.rb +1 -1
  57. data/spec/commands/data_commands_spec.rb +38 -0
  58. data/spec/commands/tree_commands_spec.rb +68 -0
  59. data/spec/fixtures/app/views/index.html.slim +1 -0
  60. data/spec/google_translate_spec.rb +5 -3
  61. data/spec/i18n_spec.rb +18 -0
  62. data/spec/i18n_tasks_spec.rb +8 -8
  63. data/spec/spec_helper.rb +3 -3
  64. data/spec/support/test_codebase.rb +4 -1
  65. data/spec/used_keys_spec.rb +7 -7
  66. data/templates/config/i18n-tasks.yml +2 -2
  67. metadata +48 -4
  68. data/lib/i18n/tasks/commands_base.rb +0 -107
  69. data/lib/i18n/tasks/fill_tasks.rb +0 -40
@@ -1,6 +1,9 @@
1
1
  # coding: utf-8
2
2
  module I18n
3
3
  module Tasks
4
+ # When this type of error is caught:
5
+ # 1. show error message of the backtrace
6
+ # 2. exit with non-zero exit code
4
7
  class CommandError < StandardError
5
8
  end
6
9
  end
@@ -1,176 +1,29 @@
1
1
  # coding: utf-8
2
- require 'i18n/tasks/commands_base'
3
- require 'i18n/tasks/reports/terminal'
4
- require 'i18n/tasks/reports/spreadsheet'
2
+ require 'i18n/tasks/command/dsl'
3
+ require 'i18n/tasks/command/collection'
4
+ require 'i18n/tasks/command/commands/health'
5
+ require 'i18n/tasks/command/commands/missing'
6
+ require 'i18n/tasks/command/commands/usages'
7
+ require 'i18n/tasks/command/commands/eq_base'
8
+ require 'i18n/tasks/command/commands/data'
9
+ require 'i18n/tasks/command/commands/tree'
10
+ require 'i18n/tasks/command/commands/meta'
11
+ require 'i18n/tasks/command/commands/xlsx'
12
+ require 'i18n/tasks/command/commander'
5
13
 
6
14
  module I18n::Tasks
7
- class Commands < CommandsBase
15
+ class Commands < Command::Commander
16
+ extend Command::DSL
17
+ include Command::Commands::Health
18
+ include Command::Commands::Missing
19
+ include Command::Commands::Usages
20
+ include Command::Commands::EqBase
21
+ include Command::Commands::Data
22
+ include Command::Commands::Tree
23
+ include Command::Commands::Meta
24
+ include Command::Commands::XLSX
25
+
8
26
  include Term::ANSIColor
9
27
  require 'highline/import'
10
-
11
- on_locale_opt = { as: Array, delimiter: /[+:,]/, default: 'all', argument: true, optional: false }
12
- OPT = {
13
- locale: proc {
14
- on '-l', :locales=,
15
- 'Filter by locale(s), comma-separated list (en,fr) or all (default), or pass arguments without -l',
16
- on_locale_opt
17
- },
18
- format: proc {
19
- on '-f', :format=,
20
- "Output format: #{VALID_TREE_FORMATS * ', '}. Default: terminal-table.",
21
- {default: 'terminal-table', argument: true, optional: false}
22
- },
23
- strict: proc {
24
- on :s, :strict, %Q(Do not infer dynamic key usage such as `t("category.\#{category.name}")`)
25
- }
26
- }
27
- desc 'show missing translations'
28
- opts do
29
- instance_exec &OPT[:locale]
30
- instance_exec &OPT[:format]
31
- on '-t', :types=, 'Filter by type (types: used, diff)', as: Array, delimiter: /[+:,]/
32
- end
33
- cmd :missing do |opt = {}|
34
- parse_locales! opt
35
- print_locale_tree i18n.missing_keys(opt), opt, :missing_keys
36
- end
37
-
38
- desc 'show unused translations'
39
- opts do
40
- instance_exec &OPT[:locale]
41
- instance_exec &OPT[:format]
42
- instance_exec &OPT[:strict]
43
- end
44
- cmd :unused do |opt = {}|
45
- parse_locales! opt
46
- print_locale_tree i18n.unused_keys(opt), opt, :unused_keys
47
- end
48
-
49
- desc 'show translations equal to base value'
50
- opts do
51
- instance_exec &OPT[:format]
52
- end
53
- cmd :eq_base do |opt = {}|
54
- parse_locales! opt
55
- print_locale_tree i18n.eq_base_keys(opt), opt, :eq_base_keys
56
- end
57
-
58
- desc 'show where the keys are used in the code'
59
- opts do
60
- on '-p', :pattern=, 'Show only keys matching pattern', argument: true, optional: false
61
- instance_exec &OPT[:format]
62
- end
63
- cmd :find do |opt = {}|
64
- opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
65
- print_locale_tree i18n.used_tree(key_filter: opt[:filter].presence, source_locations: true), opt, :used_keys
66
- end
67
-
68
- desc 'show locale data'
69
- opts do
70
- instance_exec &OPT[:locale]
71
- instance_exec &OPT[:format]
72
- end
73
- cmd :data do |opt = {}|
74
- parse_locales! opt
75
- print_locale_tree i18n.data_forest(opt[:locales]), opt
76
- end
77
-
78
- desc 'translate missing keys with Google Translate'
79
- opts do
80
- on '-l', :locales=, 'Locales to translate (comma-separated, default: all)', on_locale_opt
81
- on '-f', :from=, 'Locale to translate from (default: base)', default: 'base', argument: true, optional: false
82
- end
83
- cmd :translate_missing do |opt = {}|
84
- opt[:from] = base_locale if opt[:from].blank? || opt[:from] == 'base'
85
- parse_locales! opt
86
- i18n.fill_missing_google_translate opt
87
- end
88
-
89
- desc 'add missing keys to the locales'
90
- opts do
91
- on '-l', :locales=, 'Locales to add keys into (comma-separated, default: all)', on_locale_opt
92
- on '-p', :placeholder=, 'Value for empty keys (default: base value or key.humanize)', argument: true, optional: false
93
- end
94
- cmd :add_missing do |opt = {}|
95
- parse_locales! opt
96
- opt[:value] ||= opt.delete(:placeholder) || proc { |key, locale|
97
- # default to base value or key.humanize
98
- locale != base_locale && t(key, base_locale) || SplitKey.split_key(key).last.to_s.humanize
99
- }
100
-
101
- v = opt[:value]
102
- if v.is_a?(String) && v.include?('%{base_value}')
103
- opt[:value] = proc { |key, locale, node|
104
- base_value = node.value || t(key, base_locale) || ''
105
- v % {base_value: base_value}
106
- }
107
- end
108
-
109
- i18n.fill_missing_value opt
110
- end
111
-
112
- desc 'normalize translation data: sort and move to the right files'
113
- opts do
114
- on '-l', :locales=, 'Locales to normalize (comma-separated, default: all)', on_locale_opt
115
- on '-p', :pattern_router, 'Use pattern router, regardless of config.', argument: false, optional: true
116
- end
117
- cmd :normalize do |opt = {}|
118
- parse_locales! opt
119
- i18n.normalize_store! opt[:locales], opt[:pattern_router]
120
- end
121
-
122
- desc 'remove unused keys'
123
- opts do
124
- on '-l', :locales=, 'Locales to remove unused keys from (comma-separated, default: all)', on_locale_opt
125
- instance_exec &OPT[:strict]
126
- end
127
- cmd :remove_unused do |opt = {}|
128
- parse_locales! opt
129
- unused_keys = i18n.unused_keys(opt)
130
- if unused_keys.present?
131
- terminal_report.unused_keys(unused_keys)
132
- unless ENV['CONFIRM']
133
- exit 1 unless agree(red "#{unused_keys.leaves.count} translations will be removed in #{bold opt[:locales] * ', '}#{red '.'} " + yellow('Continue? (yes/no)') + ' ')
134
- end
135
- i18n.remove_unused!(opt[:locales])
136
- $stderr.puts "Removed #{unused_keys.leaves.count} keys"
137
- else
138
- $stderr.puts bold green 'No unused keys to remove'
139
- end
140
- end
141
-
142
- desc 'display i18n-tasks configuration'
143
- cmd :config do
144
- cfg = i18n.config_for_inspect.to_yaml
145
- cfg.sub! /\A---\n/, ''
146
- cfg.gsub! /^([^\s-].+?:)/, Term::ANSIColor.cyan(Term::ANSIColor.bold('\1'))
147
- puts cfg
148
- end
149
-
150
- desc 'save missing and unused translations to an Excel file'
151
- opts do
152
- on :path=, 'Destination path', default: 'tmp/i18n-report.xlsx'
153
- end
154
- cmd :xlsx_report do |opt = {}|
155
- begin
156
- require 'axlsx'
157
- rescue LoadError
158
- message = %Q(For spreadsheet report please add axlsx gem to Gemfile:\ngem 'axlsx', '~> 2.0')
159
- log_stderr Term::ANSIColor.red Term::ANSIColor.bold message
160
- exit 1
161
- end
162
- spreadsheet_report.save_report opt[:path]
163
- end
164
-
165
- desc 'REPL session within i18n-tasks context'
166
- cmd :irb do
167
- require 'i18n/tasks/console_context'
168
- ::I18n::Tasks::ConsoleContext.start
169
- end
170
-
171
- desc 'show path to the gem'
172
- cmd :gem_path do
173
- puts File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..'))
174
- end
175
28
  end
176
29
  end
@@ -62,22 +62,7 @@ module I18n::Tasks::Configuration
62
62
 
63
63
  # @return [Array<String>] all available locales, base_locale is always first
64
64
  def locales
65
- @config_sections[:locales] ||= begin
66
- locales = config[:locales]
67
- locales ||= data.available_locales
68
- locales = locales.map(&:to_s)
69
- locales = if locales.include?(base_locale)
70
- [base_locale] + (locales - [base_locale])
71
- else
72
- [base_locale] + locales
73
- end
74
- if config[:locales]
75
- log_verbose "config.locales set to #{locales}"
76
- else
77
- log_verbose "config.locales inferred from data #{locales}"
78
- end
79
- locales
80
- end
65
+ @config_sections[:locales] ||= data.locales
81
66
  end
82
67
 
83
68
  # @return [String] default i18n locale
@@ -37,7 +37,7 @@ module I18n::Tasks
37
37
  #{yellow 'Data as trees'}
38
38
  tree(locale)
39
39
  missing_tree(locale, compared_to = base_locale)
40
- used_tree(source_locations: false, key_filter: nil)
40
+ used_tree(source_occurrences: false, key_filter: nil)
41
41
  unused_tree(locale)
42
42
  build_tree('es' => {'hello' => 'Hola'})
43
43
 
@@ -8,17 +8,22 @@ module I18n::Tasks
8
8
  # @see I18n::Tasks::Data::FileSystem
9
9
  def data
10
10
  @data ||= begin
11
- conf = (config[:data] || {}).with_indifferent_access
12
- adapter = (conf[:adapter].presence || conf[:class].presence || :file_system).to_s
13
- if adapter !~ /[A-Z]/
14
- adapter = "I18n::Tasks::Data::#{adapter.camelize}"
15
- end
16
- adapter.constantize.new(conf.except(:adapter, :class).merge(base_locale: base_locale))
11
+ data_config = (config[:data] || {}).with_indifferent_access
12
+ data_config.merge!(base_locale: base_locale, locales: config[:locales])
13
+ adapter_class = data_config[:adapter].presence || data_config[:class].presence || :file_system
14
+ adapter_class = adapter_class.to_s
15
+ adapter_class = "I18n::Tasks::Data::#{adapter_class.camelize}" if adapter_class !~ /[A-Z]/
16
+ data_config.except!(:adapter, :class)
17
+ adapter_class.constantize.new data_config
17
18
  end
18
19
  end
19
20
 
21
+ def empty_forest
22
+ ::I18n::Tasks::Data::Tree::Siblings.new
23
+ end
24
+
20
25
  def data_forest(locales = self.locales)
21
- locales.inject(Tree::Siblings.new) do |tree, locale|
26
+ locales.inject(empty_forest) do |tree, locale|
22
27
  tree.merge! data[locale]
23
28
  end
24
29
  end
@@ -51,7 +56,7 @@ module I18n::Tasks
51
56
 
52
57
  # write to store, normalizing all data
53
58
  def normalize_store!(from = nil, pattern_router = false)
54
- from = self.locales unless from
59
+ from = self.locales unless from
55
60
  router = pattern_router ? ::I18n::Tasks::Data::Router::PatternRouter.new(data, data.config) : data.router
56
61
  data.with_router(router) do
57
62
  Array(from).each do |target_locale|
@@ -9,36 +9,41 @@ module I18n
9
9
  base.extend ClassMethods
10
10
  end
11
11
 
12
- def adapter_for(path)
13
- self.class.adapter_for(path)
14
- end
12
+ delegate :adapter_for_path, :adapter_by_name, :adapter_name, :adapter_names, to: :class
15
13
 
16
- def adapter_by_name(path)
17
- self.class.adapter_by_name(path)
14
+ def adapter_dump(tree, format)
15
+ adapter_op :dump, format, tree, write_config(format)
18
16
  end
19
17
 
20
- def adapter_names
21
- self.class.adapter_names
18
+ def adapter_parse(tree, format)
19
+ adapter_op :parse, format, tree, read_config(format)
22
20
  end
23
21
 
24
- def adapter_dump(tree, adapter_info)
25
- adapter_name, adapter_pattern, adapter = adapter_info
26
- adapter_options = (config[adapter_name] || {})[:write]
27
- adapter.dump(tree, adapter_options)
22
+ def adapter_op(op, format, tree, config)
23
+ adapter_by_name(format).send(op, tree, config)
24
+ rescue Exception => e
25
+ raise CommandError.new("#{format} #{op} error: #{e.message}")
28
26
  end
29
27
 
30
28
  protected
31
29
 
30
+ def write_config(format)
31
+ (config[format] || {})[:write]
32
+ end
33
+
34
+ def read_config(format)
35
+ (config[format] || {})[:read]
36
+ end
37
+
32
38
  def load_file(path)
33
- adapter_name, adapter_pattern, adapter = adapter_for(path)
34
- adapter_options = (config[adapter_name] || {})[:read]
35
- adapter.parse(::File.read(path), adapter_options)
39
+ adapter = adapter_for_path(path)
40
+ adapter.parse ::File.read(path), read_config(adapter_name(adapter))
36
41
  end
37
42
 
38
43
  def write_tree(path, tree)
39
44
  ::FileUtils.mkpath(File.dirname path)
40
45
  ::File.open(path, 'w') { |f|
41
- f.write(adapter_dump(tree.to_hash, adapter_for(path)))
46
+ f.write(adapter_dump(tree.to_hash, adapter_name(adapter_for_path(path))))
42
47
  }
43
48
  end
44
49
 
@@ -49,21 +54,27 @@ module I18n
49
54
  (@fn_patterns ||= []) << [name, pattern, adapter]
50
55
  end
51
56
 
52
- def adapter_for(path)
57
+ def adapter_for_path(path)
53
58
  @fn_patterns.detect { |(_name, pattern, _adapter)|
54
59
  ::File.fnmatch(pattern, path)
55
- } or raise CommandError.new("Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}")
60
+ }.try(:last) or raise CommandError.new("Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}")
56
61
  end
57
62
 
58
63
  def adapter_names
59
64
  @fn_patterns.map(&:first)
60
65
  end
61
66
 
67
+ def adapter_name(adapter)
68
+ @fn_patterns.detect { |(adapter_name, _pattern, registered_adapter)|
69
+ registered_adapter == adapter
70
+ }.try(:first) or raise CommandError.new("Adapter #{adapter.inspect} is not registered. Registered adapters: #{@fn_patterns.inspect}")
71
+ end
72
+
62
73
  def adapter_by_name(name)
63
74
  name = name.to_s
64
75
  @fn_patterns.detect { |(adapter_name, _pattern, _adapter)|
65
76
  adapter_name.to_s == name
66
- } or raise CommandError.new("Adapter with name #{name.inspect} not found. Registered adapters: #{@fn_patterns.inspect}")
77
+ }.try(:last) or raise CommandError.new("Adapter with name #{name.inspect} not found. Registered adapters: #{@fn_patterns.inspect}")
67
78
  end
68
79
  end
69
80
  end
@@ -9,8 +9,10 @@ module I18n::Tasks
9
9
  module Data
10
10
  class FileSystemBase
11
11
  include ::I18n::Tasks::Data::FileFormats
12
+ include ::I18n::Tasks::Logging
12
13
 
13
- attr_reader :config, :base_locale
14
+ attr_reader :config, :base_locale, :locales
15
+ attr_accessor :locales
14
16
 
15
17
  DEFAULTS = {
16
18
  read: ['config/locales/%{locale}.yml'],
@@ -18,8 +20,15 @@ module I18n::Tasks
18
20
  }.with_indifferent_access
19
21
 
20
22
  def initialize(config = {})
23
+ self.config = config.except(:base_locale, :locales)
21
24
  @base_locale = config[:base_locale]
22
- self.config = config.except(:base_locale)
25
+ locales = config[:locales].presence
26
+ @locales = LocaleList.normalize_locale_list(locales || available_locales, base_locale, true)
27
+ if locales.present?
28
+ log_verbose "data locales: #{@locales}"
29
+ else
30
+ log_verbose "data locales (inferred): #{@locales}"
31
+ end
23
32
  end
24
33
 
25
34
  # get locale tree
@@ -43,6 +52,28 @@ module I18n::Tasks
43
52
  @available_locales = nil
44
53
  end
45
54
 
55
+ def write(forest)
56
+ forest.each { |root| set(root.key, root) }
57
+ end
58
+
59
+ def merge!(forest)
60
+ forest.inject(Tree::Siblings.new) { |result, root|
61
+ locale = root.key
62
+ merged = get(locale).merge(root)
63
+ set locale, merged
64
+ result.merge! merged
65
+ }
66
+ end
67
+
68
+ def remove_by_key!(forest)
69
+ forest.inject(Tree::Siblings.new) do |removed, root|
70
+ locale_data = get(root.key)
71
+ subtracted = locale_data.subtract_by_key(forest)
72
+ set root.key, subtracted
73
+ removed.merge! locale_data.subtract_by_key(subtracted)
74
+ end
75
+ end
76
+
46
77
  alias []= set
47
78
 
48
79
  # @return self
@@ -56,7 +87,7 @@ module I18n::Tasks
56
87
  def available_locales
57
88
  @available_locales ||= begin
58
89
  locales = Set.new
59
- config[:read].map do |pattern|
90
+ Array(config[:read]).map do |pattern|
60
91
  [pattern, Dir.glob(pattern % {locale: '*'})] if pattern.include?('%{locale}')
61
92
  end.compact.each do |pattern, paths|
62
93
  p = pattern.gsub('\\', '\\\\').gsub('/', '\/').gsub('.', '\.')
@@ -97,7 +128,7 @@ module I18n::Tasks
97
128
  if name[0] != name[0].upcase
98
129
  name = "I18n::Tasks::Data::Router::#{name.classify}"
99
130
  end
100
- name.constantize.new(self, @config.merge(base_locale: base_locale))
131
+ name.constantize.new(self, @config.merge(base_locale: base_locale, locales: locales))
101
132
  end
102
133
  end
103
134
  attr_writer :router
@@ -6,8 +6,9 @@ module I18n::Tasks
6
6
  # Keep the path, or infer from base locale
7
7
  class ConservativeRouter < PatternRouter
8
8
  def initialize(adapter, config)
9
- @adapter = adapter
10
- @base_locale = config[:base_locale]
9
+ @adapter = adapter
10
+ @base_locale = config[:base_locale]
11
+ @locales = config[:locales]
11
12
  super
12
13
  end
13
14
 
@@ -15,18 +16,20 @@ module I18n::Tasks
15
16
  return to_enum(:route, locale, forest) unless block
16
17
  out = Hash.new { |hash, key| hash[key] = Set.new }
17
18
  not_found = Set.new
18
- forest.keys do |key, node|
19
- locale_key = "#{locale}.#{key}"
20
- path = adapter[locale][locale_key].try(:data).try(:[], :path)
21
- # infer from base
19
+ forest.keys do |key, _node|
20
+ path = key_path(locale, key)
21
+ # infer from another locale
22
22
  unless path
23
- path = base_tree["#{base_locale}.#{key}"].try(:data).try(:[], :path)
24
- path = LocalePathname.replace_locale(path, base_locale, locale)
23
+ inferred_from = (locales - [locale]).detect { |loc|
24
+ path = key_path(loc, key)
25
+ }
26
+ path = LocalePathname.replace_locale(path, inferred_from, locale) if inferred_from
25
27
  end
28
+ key_with_locale = "#{locale}.#{key}"
26
29
  if path
27
- out[path] << locale_key
30
+ out[path] << key_with_locale
28
31
  else
29
- not_found << locale_key
32
+ not_found << key_with_locale
30
33
  end
31
34
  end
32
35
 
@@ -49,7 +52,11 @@ module I18n::Tasks
49
52
  adapter[base_locale]
50
53
  end
51
54
 
52
- attr_reader :adapter, :base_locale
55
+ def key_path(locale, key)
56
+ adapter[locale]["#{locale}.#{key}"].try(:data).try(:[], :path)
57
+ end
58
+
59
+ attr_reader :adapter, :base_locale, :locales
53
60
  end
54
61
  end
55
62
  end