nulogy-gettext_i18n_rails 0.4.6.1

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 (33) hide show
  1. data/Gemfile +16 -0
  2. data/Gemfile.lock +126 -0
  3. data/Rakefile +24 -0
  4. data/Readme.md +226 -0
  5. data/VERSION +1 -0
  6. data/gettext_i18n_rails.gemspec +65 -0
  7. data/init.rb +14 -0
  8. data/lib/gettext_i18n_rails.rb +33 -0
  9. data/lib/gettext_i18n_rails/action_controller.rb +8 -0
  10. data/lib/gettext_i18n_rails/active_record.rb +19 -0
  11. data/lib/gettext_i18n_rails/backend.rb +67 -0
  12. data/lib/gettext_i18n_rails/base_parser.rb +41 -0
  13. data/lib/gettext_i18n_rails/haml_parser.rb +15 -0
  14. data/lib/gettext_i18n_rails/hamlet_parser.rb +16 -0
  15. data/lib/gettext_i18n_rails/html_safe_translations.rb +29 -0
  16. data/lib/gettext_i18n_rails/i18n_hacks.rb +25 -0
  17. data/lib/gettext_i18n_rails/model_attributes_finder.rb +108 -0
  18. data/lib/gettext_i18n_rails/railtie.rb +22 -0
  19. data/lib/gettext_i18n_rails/ruby_gettext_extractor.rb +144 -0
  20. data/lib/gettext_i18n_rails/slim_parser.rb +15 -0
  21. data/lib/gettext_i18n_rails/string_interpolate_fix.rb +20 -0
  22. data/lib/gettext_i18n_rails/tasks.rb +127 -0
  23. data/lib/tasks/gettext_rails_i18n.rake +1 -0
  24. data/spec/gettext_i18n_rails/action_controller_spec.rb +54 -0
  25. data/spec/gettext_i18n_rails/active_record_spec.rb +85 -0
  26. data/spec/gettext_i18n_rails/backend_spec.rb +56 -0
  27. data/spec/gettext_i18n_rails/haml_parser_spec.rb +39 -0
  28. data/spec/gettext_i18n_rails/hamlet_parser_spec.rb +33 -0
  29. data/spec/gettext_i18n_rails/slim_parser_spec.rb +40 -0
  30. data/spec/gettext_i18n_rails/string_interpolate_fix_spec.rb +32 -0
  31. data/spec/gettext_i18n_rails_spec.rb +84 -0
  32. data/spec/spec_helper.rb +39 -0
  33. metadata +110 -0
data/init.rb ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'config/initializers/session_store'
3
+ rescue LoadError
4
+ # weird bug, when run with rake rails reports error that session
5
+ # store is not configured, this fixes it somewhat...
6
+ end
7
+
8
+ if Rails::VERSION::MAJOR > 2
9
+ require 'gettext_i18n_rails'
10
+ else
11
+ #requires fast_gettext to be present.
12
+ #We give rails a chance to install it using rake gems:install, by loading it later.
13
+ config.after_initialize { require 'gettext_i18n_rails' }
14
+ end
@@ -0,0 +1,33 @@
1
+ module GettextI18nRails
2
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
3
+
4
+ extend self
5
+ end
6
+
7
+ require 'fast_gettext'
8
+ if Gem::Version.new(FastGettext::VERSION) < Gem::Version.new("0.4.8")
9
+ raise "Please upgrade fast_gettext"
10
+ end
11
+
12
+ # include translations into all the places it needs to go...
13
+ Object.send(:include, FastGettext::Translation)
14
+
15
+ # make translations html_safe if possible and wanted
16
+ if "".respond_to?(:html_safe?)
17
+ require 'gettext_i18n_rails/html_safe_translations'
18
+ Object.send(:include, GettextI18nRails::HtmlSafeTranslations)
19
+ end
20
+
21
+ require 'gettext_i18n_rails/backend'
22
+ I18n.backend = GettextI18nRails::Backend.new
23
+
24
+ require 'gettext_i18n_rails/i18n_hacks'
25
+
26
+ require 'gettext_i18n_rails/active_record'
27
+ # If configuration via Railties is not available force activerecord extensions
28
+ if not defined?(Rails::Railtie) and defined?(ActiveRecord)
29
+ ActiveRecord::Base.extend GettextI18nRails::ActiveRecord
30
+ end
31
+
32
+ require 'gettext_i18n_rails/action_controller' if defined?(ActionController) # so that bundle console can work in a rails project
33
+ require 'gettext_i18n_rails/railtie'
@@ -0,0 +1,8 @@
1
+ class ActionController::Base
2
+ def set_gettext_locale
3
+ requested_locale = params[:locale] || session[:locale] || cookies[:locale] || request.env['HTTP_ACCEPT_LANGUAGE'] || I18n.default_locale
4
+ locale = FastGettext.set_locale(requested_locale)
5
+ session[:locale] = locale
6
+ I18n.locale = locale # some weird overwriting in action-controller makes this necessary ... see I18nProxy
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module GettextI18nRails::ActiveRecord
2
+ # CarDealer.sales_count -> s_('CarDealer|Sales count') -> 'Sales count' if no translation was found
3
+ def human_attribute_name(attribute, *args)
4
+ s_(gettext_translation_for_attribute_name(attribute))
5
+ end
6
+
7
+ # CarDealer -> _('car dealer')
8
+ def human_name(*args)
9
+ _(self.human_name_without_translation)
10
+ end
11
+
12
+ def human_name_without_translation
13
+ self.to_s.underscore.gsub('_',' ')
14
+ end
15
+
16
+ def gettext_translation_for_attribute_name(attribute)
17
+ "#{self}|#{attribute.to_s.split('.').map! {|a| a.humanize }.join('|')}"
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ module GettextI18nRails
2
+ #translates i18n calls to gettext calls
3
+ class Backend
4
+ @@translate_defaults = true
5
+ cattr_accessor :translate_defaults
6
+ attr_accessor :backend
7
+
8
+ def initialize(*args)
9
+ self.backend = I18n::Backend::Simple.new(*args)
10
+ end
11
+
12
+ def available_locales
13
+ FastGettext.available_locales || []
14
+ end
15
+
16
+ def translate(locale, key, options)
17
+ if gettext_key = gettext_key(key, options)
18
+ translation = FastGettext._(gettext_key)
19
+ interpolate(translation, options)
20
+ else
21
+ backend.translate locale, key, options
22
+ end
23
+ end
24
+
25
+ def method_missing(method, *args)
26
+ backend.send(method, *args)
27
+ end
28
+
29
+ protected
30
+
31
+ def gettext_key(key, options)
32
+ flat_key = flatten_key key, options
33
+ if FastGettext.key_exist?(flat_key)
34
+ flat_key
35
+ elsif self.class.translate_defaults
36
+ [*options[:default]].each do |default|
37
+ #try the scoped(more specific) key first e.g. 'activerecord.errors.my custom message'
38
+ flat_key = flatten_key default, options
39
+ return flat_key if FastGettext.key_exist?(flat_key)
40
+
41
+ #try the short key thereafter e.g. 'my custom message'
42
+ return default if FastGettext.key_exist?(default)
43
+ end
44
+ return nil
45
+ end
46
+ end
47
+
48
+ def interpolate(string, values)
49
+ if string.respond_to?(:%)
50
+ reserved_keys = if defined?(I18n::RESERVED_KEYS) # rails 3+
51
+ I18n::RESERVED_KEYS
52
+ else
53
+ I18n::Backend::Base::RESERVED_KEYS
54
+ end
55
+
56
+ string % values.except(*reserved_keys)
57
+ else
58
+ string
59
+ end
60
+ end
61
+
62
+ def flatten_key key, options
63
+ scope = [*(options[:scope] || [])]
64
+ scope.empty? ? key.to_s : "#{scope*'.'}.#{key}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ require 'gettext/utils'
2
+ begin
3
+ require 'gettext/tools/rgettext'
4
+ rescue LoadError #version prior to 2.0
5
+ require 'gettext/rgettext'
6
+ end
7
+
8
+ module GettextI18nRails
9
+ class BaseParser
10
+ def self.target?(file)
11
+ File.extname(file) == ".#{extension}"
12
+ end
13
+
14
+ def self.parse(file, msgids = [])
15
+ return msgids unless load_library
16
+ code = convert_to_code(File.read(file))
17
+ RubyGettextExtractor.parse_string(code, file, msgids)
18
+ rescue Racc::ParseError => e
19
+ $stderr.puts "file ignored: ruby_parser cannot read #{extension} files with 1.9 syntax --- (#{e.message})"
20
+ return msgids
21
+ end
22
+
23
+ def self.load_library
24
+ return true if @library_loaded
25
+
26
+ begin
27
+ require "#{::Rails.root.to_s}/vendor/plugins/#{extension}/lib/#{extension}"
28
+ rescue LoadError
29
+ begin
30
+ require extension # From gem
31
+ rescue LoadError
32
+ puts "A #{extension} file was found, but #{extension} library could not be found, so nothing will be parsed..."
33
+ return false
34
+ end
35
+ end
36
+
37
+ require 'gettext_i18n_rails/ruby_gettext_extractor'
38
+ @library_loaded = true
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ require 'gettext_i18n_rails/base_parser'
2
+
3
+ module GettextI18nRails
4
+ class HamlParser < BaseParser
5
+ def self.extension
6
+ "haml"
7
+ end
8
+
9
+ def self.convert_to_code(text)
10
+ Haml::Engine.new(text).precompiled()
11
+ end
12
+ end
13
+ end
14
+
15
+ GetText::RGetText.add_parser(GettextI18nRails::HamlParser)
@@ -0,0 +1,16 @@
1
+ require 'gettext_i18n_rails/base_parser'
2
+
3
+ module GettextI18nRails
4
+ class HamletParser < BaseParser
5
+ def self.extension
6
+ "hamlet"
7
+ end
8
+
9
+ def self.convert_to_code(text)
10
+ Hamlet::Engine.new.call(text)
11
+ end
12
+ end
13
+ end
14
+
15
+ GetText::RGetText.add_parser(GettextI18nRails::HamletParser)
16
+
@@ -0,0 +1,29 @@
1
+ module GettextI18nRails
2
+ mattr_accessor :translations_are_html_safe
3
+
4
+ module HtmlSafeTranslations
5
+ # also make available on class methods
6
+ def self.included(base)
7
+ base.extend self
8
+ end
9
+
10
+ def _(*args)
11
+ html_safe_if_wanted super
12
+ end
13
+
14
+ def n_(*args)
15
+ html_safe_if_wanted super
16
+ end
17
+
18
+ def s_(*args)
19
+ html_safe_if_wanted super
20
+ end
21
+
22
+ private
23
+
24
+ def html_safe_if_wanted(text)
25
+ return text unless GettextI18nRails.translations_are_html_safe
26
+ text.to_s.html_safe
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ I18n::Config # autoload
2
+
3
+ module I18n
4
+ class Config
5
+ def locale
6
+ FastGettext.locale.to_sym
7
+ end
8
+
9
+ def locale=(new_locale)
10
+ FastGettext.locale=(new_locale)
11
+ end
12
+ end
13
+
14
+ # backport I18n.with_locale if it does not exist
15
+ # Executes block with given I18n.locale set.
16
+ def self.with_locale(tmp_locale = nil)
17
+ if tmp_locale
18
+ current_locale = self.locale
19
+ self.locale = tmp_locale
20
+ end
21
+ yield
22
+ ensure
23
+ self.locale = current_locale if tmp_locale
24
+ end unless defined? I18n.with_locale
25
+ end
@@ -0,0 +1,108 @@
1
+ module GettextI18nRails
2
+ #write all found models/columns to a file where GetTexts ruby parser can find them
3
+ def store_model_attributes(options)
4
+ file = options[:to] || 'locale/model_attributes.rb'
5
+ begin
6
+ File.open(file,'w') do |f|
7
+ f.puts "#DO NOT MODIFY! AUTOMATICALLY GENERATED FILE!"
8
+ ModelAttributesFinder.new.find(options).each do |table_name,column_names|
9
+ #model name
10
+ model = table_name_to_namespaced_model(table_name)
11
+ if model == nil
12
+ # Some tables are not models, for example: translation tables created by globalize2.
13
+ puts "[Warning] Model not found for table '#{table_name}'"
14
+ next
15
+ end
16
+ f.puts("_('#{model.human_name_without_translation}')")
17
+
18
+ #all columns namespaced under the model
19
+ column_names.each do |attribute|
20
+ translation = model.gettext_translation_for_attribute_name(attribute)
21
+ f.puts("_('#{translation}')")
22
+ end
23
+ end
24
+ f.puts "#DO NOT MODIFY! AUTOMATICALLY GENERATED FILE!"
25
+ end
26
+ rescue
27
+ puts "[Error] Attribute extraction failed. Removing incomplete file (#{file})"
28
+ File.delete(file)
29
+ raise
30
+ end
31
+ end
32
+ module_function :store_model_attributes
33
+
34
+ class ModelAttributesFinder
35
+ # options:
36
+ # :ignore_tables => ['cars',/_settings$/,...]
37
+ # :ignore_columns => ['id',/_id$/,...]
38
+ # current connection ---> {'cars'=>['model_name','type'],...}
39
+ def find(options)
40
+ found = Hash.new([])
41
+
42
+ connection = ::ActiveRecord::Base.connection
43
+ connection.tables.each do |table_name|
44
+ next if ignored?(table_name,options[:ignore_tables])
45
+ connection.columns(table_name).each do |column|
46
+ found[table_name] += [column.name] unless ignored?(column.name,options[:ignore_columns])
47
+ end
48
+ end
49
+
50
+ found
51
+ end
52
+
53
+ def ignored?(name,patterns)
54
+ return false unless patterns
55
+ patterns.detect{|p|p.to_s==name.to_s or (p.is_a?(Regexp) and name=~p)}
56
+ end
57
+ end
58
+
59
+ private
60
+ # Tries to find the model class corresponding to specified table name.
61
+ # Takes into account that the model can be defined in a namespace.
62
+ # Searches only up to one level deep - won't find models nested in two
63
+ # or more modules.
64
+ #
65
+ # Note that if we allow namespaces, the conversion can be ambiguous, i.e.
66
+ # if the table is named "aa_bb_cc" and AaBbCc, Aa::BbCc and AaBb::Cc are
67
+ # all defined there's no absolute rule that tells us which one to use.
68
+ # This method prefers the less nested one and, if there are two at
69
+ # the same level, the one with shorter module name.
70
+ def table_name_to_namespaced_model(table_name)
71
+ # First assume that there are no namespaces
72
+ model = to_class(table_name.singularize.camelcase)
73
+ return model if model != nil
74
+
75
+ # If you were wrong, assume that the model is in a namespace.
76
+ # Iterate over the underscores and try to substitute each of them
77
+ # for a slash that camelcase() replaces with the scope operator (::).
78
+ underscore_position = table_name.index('_')
79
+ while underscore_position != nil
80
+ namespaced_table_name = table_name.dup
81
+ namespaced_table_name[underscore_position] = '/'
82
+ model = to_class(namespaced_table_name.singularize.camelcase)
83
+ return model if model != nil
84
+
85
+ underscore_position = table_name.index('_', underscore_position + 1)
86
+ end
87
+
88
+ # The model either is not defined or is buried more than one level
89
+ # deep in a module hierarchy
90
+ return nil
91
+ end
92
+
93
+ # Checks if there is a class of specified name and if so, returns
94
+ # the class object. Otherwise returns nil.
95
+ def to_class(name)
96
+ # I wanted to use Module.const_defined?() here to avoid relying
97
+ # on exceptions for normal program flow but it's of no use.
98
+ # If class autoloading is enabled, the constant may be undefined
99
+ # but turn out to be present when we actually try to use it.
100
+ begin
101
+ constant = name.constantize
102
+ rescue NameError
103
+ return nil
104
+ end
105
+
106
+ return constant.is_a?(Class) ? constant : nil
107
+ end
108
+ end
@@ -0,0 +1,22 @@
1
+ # add rake tasks if we are inside Rails
2
+ if defined?(Rails::Railtie)
3
+ module GettextI18nRails
4
+ class Railtie < ::Rails::Railtie
5
+ config.gettext_i18n_rails = ActiveSupport::OrderedOptions.new
6
+ config.gettext_i18n_rails.msgmerge = %w[--sort-output --no-location --no-wrap]
7
+ config.gettext_i18n_rails.use_for_active_record_attributes = true
8
+
9
+ rake_tasks do
10
+ require 'gettext_i18n_rails/tasks'
11
+ end
12
+
13
+ config.after_initialize do |app|
14
+ if app.config.gettext_i18n_rails.use_for_active_record_attributes
15
+ ActiveSupport.on_load :active_record do
16
+ extend GettextI18nRails::ActiveRecord
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,144 @@
1
+ # new ruby parser from retoo, that should help extracting "#{_('xxx')}", which is needed especially when parsing haml files
2
+ #
3
+ #!/usr/bin/ruby
4
+ # parser/ruby.rb - look for gettext msg strings in ruby files
5
+
6
+ require 'rubygems'
7
+ require 'ruby_parser'
8
+
9
+ begin
10
+ require 'gettext/tools/rgettext'
11
+ rescue LoadError #version prior to 2.0
12
+ require 'gettext/rgettext'
13
+ end
14
+
15
+ module RubyGettextExtractor
16
+ extend self
17
+
18
+ def parse(file, targets = []) # :nodoc:
19
+ content = File.read(file)
20
+ parse_string(content, file, targets)
21
+ end
22
+
23
+ def parse_string(content, file, targets=[])
24
+ # file is just for information in error messages
25
+ parser = Extractor.new(file, targets)
26
+ parser.run(content)
27
+ end
28
+
29
+ def target?(file) # :nodoc:
30
+ return file =~ /\.rb$/
31
+ end
32
+
33
+ class Extractor < RubyParser
34
+ def initialize(filename, targets)
35
+ @filename = filename
36
+ @targets = Hash.new
37
+ @results = []
38
+
39
+ targets.each do |a|
40
+ k, v = a
41
+ # things go wrong if k already exists, but this
42
+ # should not happen (according to the gettext doc)
43
+ @targets[k] = a
44
+ @results << a
45
+ end
46
+
47
+ super()
48
+ end
49
+
50
+ def run(content)
51
+ # ruby parser has an ugly bug which causes that several \000's take
52
+ # ages to parse. This avoids this probelm by stripping them away (they probably wont appear in keys anyway)
53
+ # See bug report: http://rubyforge.org/tracker/index.php?func=detail&aid=26898&group_id=439&atid=1778
54
+ safe_content = content.gsub(/\\\d\d\d/, '')
55
+ self.parse(safe_content)
56
+ return @results
57
+ end
58
+
59
+ def extract_string(node)
60
+ if node.first == :str
61
+ return node.last
62
+ elsif node.first == :call
63
+ type, recv, meth, args = node
64
+
65
+ # node has to be in form of "string"+("other_string")
66
+ return nil unless recv && meth == :+
67
+
68
+ # descent recurrsivly to determine the 'receiver' of the string concatination
69
+ # "foo" + "bar" + baz" is
70
+ # ("foo".+("bar")).+("baz")
71
+ first_part = extract_string(recv)
72
+
73
+ if args.first == :arglist && args.size == 2
74
+ second_part = extract_string(args.last)
75
+
76
+ return nil if second_part.nil?
77
+
78
+ return first_part.to_s + second_part.to_s
79
+ else
80
+ raise "uuh?"
81
+ end
82
+ else
83
+ return nil
84
+ end
85
+ end
86
+
87
+ def extract_key(args, seperator)
88
+ key = nil
89
+ if args.size == 2
90
+ key = extract_string(args.value)
91
+ else
92
+ # this could be n_("aaa","aaa2",1)
93
+ # all strings arguemnts are extracted and joined with \004 or \000
94
+
95
+ arguments = args[1..(-1)]
96
+
97
+ res = []
98
+ arguments.each do |a|
99
+ str = extract_string(a)
100
+ # only add strings
101
+ res << str if str
102
+ end
103
+
104
+ return nil if res.empty?
105
+ key = res.join(seperator)
106
+ end
107
+
108
+ return nil unless key
109
+
110
+ key.gsub!("\n", '\n')
111
+ key.gsub!("\t", '\t')
112
+ key.gsub!("\0", '\0')
113
+
114
+ return key
115
+ end
116
+
117
+ def new_call recv, meth, args = nil
118
+ # we dont care if the method is called on a a object
119
+ if recv.nil?
120
+ if (meth == :_ || meth == :p_ || meth == :N_ || meth == :pgettext || meth == :s_)
121
+ key = extract_key(args, "\004")
122
+ elsif meth == :n_
123
+ key = extract_key(args, "\000")
124
+ else
125
+ # skip
126
+ end
127
+
128
+ if key
129
+ res = @targets[key]
130
+
131
+ unless res
132
+ res = [key]
133
+ @results << res
134
+ @targets[key] = res
135
+ end
136
+
137
+ res << "#{@filename}:#{lexer.lineno}"
138
+ end
139
+ end
140
+
141
+ super recv, meth, args
142
+ end
143
+ end
144
+ end