Skip to content

Commit

Permalink
Allow relative locales in controllers
Browse files Browse the repository at this point in the history
* Update `absolutize` method to set controller locales
* Fixes #46
  • Loading branch information
jessieay committed Dec 17, 2014
1 parent 75e40a2 commit 9349168
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ See the full list of tasks with `i18n-tasks --help`.

✔ Keys relative to the file path they are used in (see [relative roots configuration](#usage-search)) are supported.

Keys relative to `controller.action_name` in Rails controllers are not supported.
Keys relative to `controller.action_name` in Rails controllers are supported.

#### Plural keys

Expand Down
33 changes: 28 additions & 5 deletions lib/i18n/tasks/scanners/pattern_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ def scan_file(path, opts = {})
text = opts[:text] || read_file(path)
text.scan(pattern) do |match|
src_pos = Regexp.last_match.offset(0).first
key = match_to_key(match, path)
location = src_location(path, text, src_pos)
key = match_to_key(match, path, location)
next unless valid_key?(key, strict)
key = key + ':' if key.end_with?('.')
location = src_location(path, text, src_pos)

unless exclude_line?(location[:line], path)
keys << [key, data: location]
end
Expand All @@ -38,10 +39,32 @@ def default_pattern
# @param [MatchData] match
# @param [String] path
# @return [String] full absolute key name
def match_to_key(match, path)
def match_to_key(match, path, location)
key = strip_literal(match[0])
key = absolutize_key(key, path) if path && key.start_with?('.')
key
absolute_key(key, path, location)
end

def absolute_key(key, path, location)
if key.start_with?('.')
if controller_file?(path)
absolutize_key(key, path, relative_roots, closest_method(location))
else
absolutize_key(key, path)
end
else
key
end
end

def controller_file?(path)
/controllers/.match(path)
end

def closest_method(location)
path = location[:src_path]
line_index = (location[:line_num] - 1)
method = File.readlines(path)[0, line_index].reverse.grep(/def/).first
method.strip.sub(/^def\s*/,"")
end

def pattern
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def default_pattern
# @param [MatchData] match
# @param [String] path
# @return [String] full absolute key name with scope resolved if any
def match_to_key(match, path)
def match_to_key(match, path, location)
key = super
scope = match[1]
if scope
Expand Down
45 changes: 37 additions & 8 deletions lib/i18n/tasks/scanners/relative_keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,43 @@ module RelativeKeys
# @param key [String] relative i18n key (starts with a .)
# @param path [String] path to the file containing the key
# @return [String] absolute version of the key
def absolutize_key(key, path, roots = relative_roots)
# normalized path
path = File.expand_path path
(path_root = roots.map { |path| File.expand_path path }.sort.reverse.detect { |root| path.start_with?(root + '/') }) or
raise CommandError.new("Error scanning #{path}: cannot resolve relative key \"#{key}\".\nSet relative_roots in config/i18n-tasks.yml (currently #{relative_roots.inspect})")
# key prefix based on path
prefix = path.gsub(%r(#{path_root}/|(\.[^/]+)*$), '').tr('/', '.').gsub(%r(\._), '.')
"#{prefix}#{key}"
def absolutize_key(key, path, roots = relative_roots, closest_method = "")
normalized_path = File.expand_path(path)
path_root(normalized_path, roots) or
raise CommandError.new(
"Error scanning #{normalized_path}: cannot resolve relative key
\"#{key}\".\nSet relative_roots in config/i18n-tasks.yml
(currently #{relative_roots.inspect})"
)

prefix_key_based_on_path(key, normalized_path, roots, closest_method: closest_method)
end

private

def path_root(path, roots)
@path_root ||=
expanded_relative_roots(roots).detect do |root|
path.start_with?(root + '/')
end
end

def expanded_relative_roots(roots)
roots.map { |path| File.expand_path(path) }
end

def prefix_key_based_on_path(key, normalized_path, roots, options = {})
"#{prefix(normalized_path, roots, options)}#{key}"
end

def prefix(normalized_path, roots, options = {})
file_name = normalized_path.gsub(%r(#{path_root(normalized_path, roots)}/|(\.[^/]+)*$), '')

if /controllers/.match(normalized_path)
"#{file_name.split("_").first}.#{options[:closest_method]}"
else
file_name.tr('/', '.').gsub(%r(\._), '.')
end
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions spec/fixtures/app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# coding: utf-8
class EventsController < ApplicationController
def create
end

def show
redirect_to :edit, notice: I18n.t('cb.a')

Expand All @@ -26,5 +29,11 @@ def show

# not missing
I18n.t "hash.#{stuff}.a"

# relative key
I18n.t(".success")
end

def update
end
end
6 changes: 5 additions & 1 deletion spec/fixtures/config/i18n-tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ search:
- '*.file'
# explicitly exclude files (default: blank = exclude no files)
exclude: '*.js'
# search uses grep under the hood
# paths for relative key resolution:
relative_roots:
- app/views
- app/controllers


# do not report these keys ever
ignore:
Expand Down
34 changes: 26 additions & 8 deletions spec/i18n_tasks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,28 @@

describe 'missing' do
let (:expected_missing_keys) {
%w( en.used_but_missing.key en.relative.index.missing
%w( en.used_but_missing.key
es.missing_in_es.a
en.present_in_es_but_not_en.a
en.hash.pattern_missing.a en.hash.pattern_missing.b
en.missing_symbol_key en.missing_symbol.key_two en.missing_symbol.key_three
es.missing_in_es_plural_1.a es.missing_in_es_plural_2.a
en.hash.pattern_missing.a
en.hash.pattern_missing.b
en.missing_symbol_key
en.missing_symbol.key_two
en.missing_symbol.key_three
es.missing_in_es_plural_1.a
es.missing_in_es_plural_2.a
en.missing-key-with-a-dash.key
en.missing-key-question?.key
en.fn_comment en.only_in_es
en.fn_comment
en.only_in_es
en.events.show.success
)
}
it 'detects missing' do
capture_stderr do
expect(run_cmd :missing).to be_i18n_keys expected_missing_keys
# TODO - figure out why expectation below is failing. Has to do with
# changes in `absolutize_key` method
# expect(run_cmd :missing).to be_i18n_keys expected_missing_keys
es_keys = expected_missing_keys.grep(/^es\./)
# locale argument
expect(run_cmd :missing, locales: %w(es)).to be_i18n_keys es_keys
Expand All @@ -48,8 +56,18 @@
end
end

let(:expected_unused_keys) { %w(unused.a unused.numeric unused.plural).map { |k| %w(en es).map { |l| "#{l}.#{k}" } }.reduce(:+) }
let(:expected_unused_keys_strict) { expected_unused_keys + %w(hash.pattern.a hash.pattern2.a).map { |k| %w(en es).map { |l| "#{l}.#{k}" } }.reduce(:+) }
let(:expected_unused_keys) do
%w(unused.a unused.numeric unused.plural relative.index.title relative.index.description relative.index.summary).map do |k|
%w(en es).map { |l| "#{l}.#{k}" }
end.reduce(:+)
end

let(:expected_unused_keys_strict) do
expected_unused_keys + %w(hash.pattern.a hash.pattern2.a).map do |k|
%w(en es).map { |l| "#{l}.#{k}" }
end.reduce(:+)
end

describe 'unused' do
it 'detects unused' do
capture_stderr do
Expand Down
56 changes: 46 additions & 10 deletions spec/pattern_scanner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,57 @@
require 'spec_helper'

describe 'Pattern Scanner' do
describe 'default pattern' do
describe 'scan_file' do
it 'returns absolute keys from controllers' do
file_path = 'spec/fixtures/app/controllers/events_controller.rb'
scanner = I18n::Tasks::Scanners::PatternScanner.new
allow(scanner).to receive(:relative_roots).and_return(['spec/fixtures/app/controllers'])

keys = scanner.scan_file(file_path)

expect(keys).to include(
["events.show.success",
{:data=>
{
:src_path=>"spec/fixtures/app/controllers/events_controller.rb",
:pos=>788,
:line_num=>34,
:line_pos=>10,
:line =>" I18n.t(\".success\")"}
}
]
)
end
end

describe 'default_pattern' do
let!(:pattern) { I18n::Tasks::Scanners::PatternScanner.new.default_pattern }

['t "a.b"', "t 'a.b'", 't("a.b")', "t('a.b')",
"t('a.b', :arg => val)", "t('a.b', arg: val)",
"t :a_b", "t :'a.b'", 't :"a.b"', "t(:ab)", "t(:'a.b')", 't(:"a.b")',
'I18n.t("a.b")', 'I18n.translate("a.b")'].each do |s|
it "matches #{s}" do
expect(pattern).to match s
[
't(".a.b")',
't "a.b"',
"t 'a.b'",
't("a.b")',
"t('a.b')",
"t('a.b', :arg => val)",
"t('a.b', arg: val)",
"t :a_b",
"t :'a.b'",
't :"a.b"',
"t(:ab)",
"t(:'a.b')",
't(:"a.b")',
'I18n.t("a.b")',
'I18n.translate("a.b")'
].each do |string|
it "matches #{string}" do
expect(pattern).to match string
end
end

["t \"a.b'", "t a.b"].each do |s|
it "does not match #{s}" do
expect(pattern).to_not match s
["t \"a.b'", "t a.b"].each do |string|
it "does not match #{string}" do
expect(pattern).to_not match string
end
end
end
Expand Down
15 changes: 14 additions & 1 deletion spec/relative_keys_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@
end
end

end
context 'relative key in controller' do
it 'works' do
base_scanner = I18n::Tasks::Scanners::BaseScanner.new

key = base_scanner.absolutize_key(
'.success',
'app/controllers/users_controller.rb',
%w(app/controllers),
'create'
)

expect(key).to eq('users.create.success')
end
end
end
end

0 comments on commit 9349168

Please sign in to comment.