Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow relative locales in controllers #113

Merged
merged 1 commit into from
Dec 17, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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