zlocalize 4.1.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.
- checksums.yaml +7 -0
- data/lib/zlocalize/backend.rb +246 -0
- data/lib/zlocalize/config.rb +62 -0
- data/lib/zlocalize/harvester.rb +123 -0
- data/lib/zlocalize/rails/active_record.rb +11 -0
- data/lib/zlocalize/rails/attached_translations.rb +122 -0
- data/lib/zlocalize/rails/decimal_attributes.rb +76 -0
- data/lib/zlocalize/rails/generators/initializer.rb +21 -0
- data/lib/zlocalize/rails/generators/templates/initializer_template.rb +53 -0
- data/lib/zlocalize/rails/generators/templates/translations_migration_template.rb +21 -0
- data/lib/zlocalize/rails/generators/translations_migration.rb +27 -0
- data/lib/zlocalize/rails/railtie.rb +28 -0
- data/lib/zlocalize/rails/tasks/harvest.rake +43 -0
- data/lib/zlocalize/rails/translated_columns.rb +73 -0
- data/lib/zlocalize/rails/translation.rb +10 -0
- data/lib/zlocalize/rails/translation_validator.rb +44 -0
- data/lib/zlocalize/source_parser.rb +756 -0
- data/lib/zlocalize/translation_file.rb +248 -0
- data/lib/zlocalize.rb +9 -0
- metadata +117 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 3ce7804e560ece2fb93d685c350d22beea6e1fdf
         | 
| 4 | 
            +
              data.tar.gz: 0cc0ca863ae4fec576455a364a76c2a5840eba27
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: a362c71b9dbb6d90cb37c60005066a85f92360879aef6a734622c7a70ff14723c9e14f6b84c513c2472fb5b2228ab9eec18f26e3949b4078b111930b3ca7d1cd
         | 
| 7 | 
            +
              data.tar.gz: 81ea38ffc8fbf24ee4ed99f6b011e33af4a2d2e52fc7ddb6e3f8248d022f0738eff2a87424588dea9a38bde47f99832fb12d030ed949c1d8ee5e3c95260ba8a3
         | 
| @@ -0,0 +1,246 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'yaml'
         | 
| 4 | 
            +
            require 'zlocalize/translation_file'
         | 
| 5 | 
            +
            require 'zlocalize/config'
         | 
| 6 | 
            +
            require 'i18n'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module ZLocalize
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              class MissingTranslationDataError < StandardError
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(locale, key, options)
         | 
| 13 | 
            +
                  super "No translation exists in '#{locale}' for key \"#{key}\""
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              @@load_mutex = Mutex.new
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              class << self
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                INTERPOLATE_MATCH = /(\\)?\{\{([^\}]+)\}\}/
         | 
| 23 | 
            +
                SCOPE_MATCH = /(.*?)(\\)?::(.+)/
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                DEFAULT_PLURAL_SELECT_PROC = lambda { |n| n <= 0 ? 0 : (n > 1 ? 2 : 1) }
         | 
| 26 | 
            +
                DEFAULT_TITLEIZE_PROC      = lambda { |s| s.to_s.titleize }
         | 
| 27 | 
            +
                DEFAULT_CONVERT_FLOAT_PROC = lambda { |s| s.to_s.gsub(",", "") }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def config
         | 
| 30 | 
            +
                  @config ||= ZLocalize::Config.new
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def locale
         | 
| 34 | 
            +
                  I18n.locale
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def locale=(value)
         | 
| 38 | 
            +
                  I18n.locale = value
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def default_locale
         | 
| 42 | 
            +
                  I18n.default_locale
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def default_locale=(value)
         | 
| 46 | 
            +
                  I18n.default_locale = value
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def translate(key, options = {})
         | 
| 50 | 
            +
                  loc = options[:locale] || self.locale
         | 
| 51 | 
            +
                  translate_with_locale(loc,key,options)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                alias :t :translate
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def pluralize(key,count,options = {})
         | 
| 56 | 
            +
                  loc = options[:locale] || self.locale
         | 
| 57 | 
            +
                  pluralize_with_locale(loc,key,count,options)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
                alias :p :pluralize
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Change locale inside a block
         | 
| 62 | 
            +
                def switch_locale(new_locale,&block)
         | 
| 63 | 
            +
                  @@switch_locale_stack ||= []
         | 
| 64 | 
            +
                  if block_given?
         | 
| 65 | 
            +
                    @@switch_locale_stack.push self.locale
         | 
| 66 | 
            +
                    self.locale = new_locale
         | 
| 67 | 
            +
                    yield
         | 
| 68 | 
            +
                    self.locale = @@switch_locale_stack.pop
         | 
| 69 | 
            +
                  else
         | 
| 70 | 
            +
                    self.locale = new_locale
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def translate_with_locale(locale, key, options = {})
         | 
| 75 | 
            +
                  entry = lookup(locale,"#{key}")
         | 
| 76 | 
            +
                  if entry.nil?
         | 
| 77 | 
            +
                    if (default = options.delete(:default))
         | 
| 78 | 
            +
                      entry = default
         | 
| 79 | 
            +
                    elsif (options.delete(:return_source_on_missing) || @return_source_on_missing) == true
         | 
| 80 | 
            +
                      entry = remove_scope(key)
         | 
| 81 | 
            +
                    else
         | 
| 82 | 
            +
                      raise ZLocalize::MissingTranslationDataError.new(locale, key, options)
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                  return interpolate(locale, entry, options).html_safe
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                # n_(["...","...","..."],count)
         | 
| 89 | 
            +
                def pluralize_with_locale(locale, key, count, options = {})
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  entry = lookup(locale,key)
         | 
| 92 | 
            +
                  if entry.nil?
         | 
| 93 | 
            +
                    if (default = options.delete(:default))
         | 
| 94 | 
            +
                      entry = default
         | 
| 95 | 
            +
                    elsif (options.delete(:return_source_on_missing) || @return_source_on_missing) == true
         | 
| 96 | 
            +
                      entry = key
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      raise ZLocalize::MissingTranslationDataError.new(locale,key,options)
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                  n = translation_procs[locale.to_sym][:plural_select].call(count)
         | 
| 102 | 
            +
                  options[:count] ||= count
         | 
| 103 | 
            +
                  return interpolate(locale, entry[n], options).html_safe
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def titleize(locale,s)
         | 
| 107 | 
            +
                  translation_procs[locale.to_sym][:titleize].call(s).html_safe
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def convert_float(locale,s)
         | 
| 111 | 
            +
                  translation_procs[locale.to_sym][:convert_float].call(s)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def multi_lookup(locale,values,options = {})
         | 
| 115 | 
            +
                  result = {}
         | 
| 116 | 
            +
                  values.each do |val|
         | 
| 117 | 
            +
                    s = lookup(locale,"#{val}")
         | 
| 118 | 
            +
                    result[val] = s.html_safe unless s.nil?
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                  result
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                # Interpolates values into a given string.
         | 
| 124 | 
            +
                #
         | 
| 125 | 
            +
                #   interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
         | 
| 126 | 
            +
                #   # => "file test.txt opened by {{user}}"
         | 
| 127 | 
            +
                #
         | 
| 128 | 
            +
                # Note that you have to double escape the <tt>\\</tt> when you want to escape
         | 
| 129 | 
            +
                # the <tt>{{...}}</tt> key in a string (once for the string and once for the
         | 
| 130 | 
            +
                # interpolation).
         | 
| 131 | 
            +
                def interpolate(locale, string, values = {})
         | 
| 132 | 
            +
                  return string unless string.is_a?(String)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  result = string.gsub(INTERPOLATE_MATCH) do
         | 
| 135 | 
            +
                    escaped, pattern, key = $1, $2, $2.to_sym
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    if escaped
         | 
| 138 | 
            +
                      "{{#{pattern}}}"
         | 
| 139 | 
            +
                    elsif !values.include?(key)
         | 
| 140 | 
            +
                      raise ArgumentError, "The #{key} argument is missing in the interpolation of '#{string}'"
         | 
| 141 | 
            +
                    else
         | 
| 142 | 
            +
                      values[key].to_s
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  result
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def initialized?
         | 
| 150 | 
            +
                  @initialized ||= false
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                def reload!(force = false)
         | 
| 154 | 
            +
                  if force || !@initialized || (self.config.reload_per_request[Rails.env.to_sym] == true)
         | 
| 155 | 
            +
                    @initialized = false
         | 
| 156 | 
            +
                    @translations = nil
         | 
| 157 | 
            +
                    @translation_procs = nil
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def locales
         | 
| 162 | 
            +
                  init_translations unless initialized?
         | 
| 163 | 
            +
                  translations.keys.map { |k| k.to_s }
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                alias :available_locales :locales
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                def translations
         | 
| 169 | 
            +
                  init_translations unless initialized?
         | 
| 170 | 
            +
                  @translations
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                def translation_procs
         | 
| 174 | 
            +
                  init_translations unless initialized?
         | 
| 175 | 
            +
                  @translation_procs
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                protected
         | 
| 179 | 
            +
                  # init_translations manipulates the translation data and is triggered by lazy-loading, meaning
         | 
| 180 | 
            +
                  # that on a busy multi-threaded environment, you might get 2 different threads trying to load the
         | 
| 181 | 
            +
                  # data concurrently. Therefore, this method is wrapped inside a mutex, which effectively ensures
         | 
| 182 | 
            +
                  # only 1 Thread will initialize the translation data Hash.
         | 
| 183 | 
            +
                  # The rest of all operations on translation data is read-only, so no concurrency issues except here
         | 
| 184 | 
            +
                  def init_translations
         | 
| 185 | 
            +
                    @@load_mutex.synchronize do
         | 
| 186 | 
            +
                      unless @initialized
         | 
| 187 | 
            +
                        @translation_procs = {}
         | 
| 188 | 
            +
                        @translations = {}
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                        config.locales.keys.each do |locale|
         | 
| 191 | 
            +
                          cfg = self.config.locales[locale]
         | 
| 192 | 
            +
                          loc = locale.to_sym
         | 
| 193 | 
            +
                          @translation_procs[loc] = {
         | 
| 194 | 
            +
                            :plural_select => cfg[:plural_select].is_a?(Proc) ? cfg[:plural_select] : DEFAULT_PLURAL_SELECT_PROC,
         | 
| 195 | 
            +
                            :titleize      => cfg[:titleize].is_a?(Proc) ? cfg[:titleize] : DEFAULT_TITLEIZE_PROC,
         | 
| 196 | 
            +
                            :convert_float => cfg[:convert_float].is_a?(Proc) ? cfg[:convert_float] : DEFAULT_CONVERT_FLOAT_PROC,
         | 
| 197 | 
            +
                          }
         | 
| 198 | 
            +
                          files = [cfg[:translations]].flatten.compact
         | 
| 199 | 
            +
                          load_translations(loc,files)
         | 
| 200 | 
            +
                        end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                        @return_source_on_missing = config.return_source_on_missing[Rails.env.to_sym]
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                        @initialized = true
         | 
| 205 | 
            +
                      end
         | 
| 206 | 
            +
                    end
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  def load_translations(locale,filenames)
         | 
| 210 | 
            +
                    @translations ||= {}
         | 
| 211 | 
            +
                    @translations[locale] ||= {}
         | 
| 212 | 
            +
                    filenames.each do |file|
         | 
| 213 | 
            +
                      unless file.nil?
         | 
| 214 | 
            +
                        tf = ZLocalize::TranslationFile.new
         | 
| 215 | 
            +
                        tf.load(file)
         | 
| 216 | 
            +
                        tf.entries.each do |key,entry|
         | 
| 217 | 
            +
                          @translations[locale][entry.source] = entry.translation unless entry.ignore || entry.translation.to_s.empty?
         | 
| 218 | 
            +
                        end
         | 
| 219 | 
            +
                      end
         | 
| 220 | 
            +
                    end
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  def lookup(locale, key)
         | 
| 224 | 
            +
                    return unless key
         | 
| 225 | 
            +
                    init_translations unless @initialized
         | 
| 226 | 
            +
                    return @translations[locale.to_sym][key]
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  def remove_scope(key)
         | 
| 230 | 
            +
                    return key unless key.is_a?(String)
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    result = key.gsub(SCOPE_MATCH) do
         | 
| 233 | 
            +
                      scope, escaped, unscoped = $1, $2, $3
         | 
| 234 | 
            +
                      if $2 # escaped with \\::
         | 
| 235 | 
            +
                        # $~[0]
         | 
| 236 | 
            +
                        scope + '::' + $3
         | 
| 237 | 
            +
                      else
         | 
| 238 | 
            +
                        $3
         | 
| 239 | 
            +
                      end
         | 
| 240 | 
            +
                    end
         | 
| 241 | 
            +
                  end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
              end # class << self
         | 
| 244 | 
            +
             | 
| 245 | 
            +
            end # module ZLocalize
         | 
| 246 | 
            +
             | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
            module ZLocalize
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              class Config
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                attr_reader :reload_per_request
         | 
| 7 | 
            +
                attr_reader :return_source_on_missing
         | 
| 8 | 
            +
                attr_reader :reload_per_request
         | 
| 9 | 
            +
                attr_reader :locales
         | 
| 10 | 
            +
                attr_reader :define_gettext_methods
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize
         | 
| 13 | 
            +
                  @reload_per_request = { :development => true, :test => false, :production => false, :staging => false }
         | 
| 14 | 
            +
                  @return_source_on_missing = { :development => true, :test => false, :production => false, :staging => false }
         | 
| 15 | 
            +
                  @locales = {}
         | 
| 16 | 
            +
                  @use_global_gettext_methods = true
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def reload_per_request=(value)
         | 
| 20 | 
            +
                  if value === true || value === false
         | 
| 21 | 
            +
                    [:development,:test,:production,:staging].each do |env|
         | 
| 22 | 
            +
                      @reload_per_request[env] = value
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  elsif value.is_a?(Hash)
         | 
| 25 | 
            +
                    @reload_per_request.merge!(value)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def return_source_on_missing=(value)
         | 
| 30 | 
            +
                  if value === true || value === false
         | 
| 31 | 
            +
                    [:development,:test,:production,:staging].each do |env|
         | 
| 32 | 
            +
                      @return_source_on_missing[env] = value
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  elsif value.is_a?(Hash)
         | 
| 35 | 
            +
                    @return_source_on_missing.merge!(value)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def locales=(value)
         | 
| 40 | 
            +
                  @locales = value
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def define_gettext_methods=(value)
         | 
| 44 | 
            +
                  @define_gettext_methods = value ? true : false
         | 
| 45 | 
            +
                  please_define_gettext_methods if @define_gettext_methods
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def please_define_gettext_methods
         | 
| 49 | 
            +
                  Object.class_eval <<-EOV
         | 
| 50 | 
            +
                     def _(key,options = {})
         | 
| 51 | 
            +
                       ZLocalize.translate(key, options)
         | 
| 52 | 
            +
                     end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                     def n_(key,count,options = {})
         | 
| 55 | 
            +
                       ZLocalize.pluralize(key,count,options)
         | 
| 56 | 
            +
                     end
         | 
| 57 | 
            +
                  EOV
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              end # class Config
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            end # module ZLocalize
         | 
| @@ -0,0 +1,123 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rubygems'
         | 
| 4 | 
            +
            require 'erb'
         | 
| 5 | 
            +
            require File.join(File.dirname(__FILE__),'source_parser')
         | 
| 6 | 
            +
            require File.join(File.dirname(__FILE__),'translation_file')
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module ZLocalize
         | 
| 9 | 
            +
             | 
| 10 | 
            +
               INDENT_STEP = 3
         | 
| 11 | 
            +
             | 
| 12 | 
            +
               class HarvesterError < StandardError
         | 
| 13 | 
            +
               end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
               # Harvester will parse and extract all calls to translation methods in "app/models/",
         | 
| 16 | 
            +
               # "app/controllers/" and "app/views/"
         | 
| 17 | 
            +
               class Harvester
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                 attr_accessor :rails_root
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                 DEFAULT_HARVEST_OPTIONS = { :models => true, :controllers => true,
         | 
| 22 | 
            +
                                             :views => true, :helpers => true,
         | 
| 23 | 
            +
                                             :output => 'config/locales/app-strings.yml',
         | 
| 24 | 
            +
                                             :overwrite_existing => false,
         | 
| 25 | 
            +
                                             :add_paths   => [],
         | 
| 26 | 
            +
                                             :silent => false,
         | 
| 27 | 
            +
                                             :purge => false }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                 HARVEST_SECTIONS = { :models      => [ "app/models/**/*.rb" ],
         | 
| 30 | 
            +
                                      :controllers => [ "app/controllers/**/*.rb" ],
         | 
| 31 | 
            +
                                      :helpers     => [ "app/helpers/**/*.rb" ],
         | 
| 32 | 
            +
                                      :views       => [ "app/views/**/*.erb", "app/views/**/*.rhtml" ],
         | 
| 33 | 
            +
                                      :lib         => [ "lib/**/*.rb"]
         | 
| 34 | 
            +
                                    }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                 def initialize(rails_root, options = {})
         | 
| 37 | 
            +
                    @rails_root = rails_root
         | 
| 38 | 
            +
                    @options = DEFAULT_HARVEST_OPTIONS.merge(options)
         | 
| 39 | 
            +
                 end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                 def progress(msg)
         | 
| 42 | 
            +
                    print msg unless @options[:silent]
         | 
| 43 | 
            +
                 end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                 def harvest_section(section,filespecs)
         | 
| 46 | 
            +
                   progress("Harvesting localizable strings from #{section}:\n")
         | 
| 47 | 
            +
                   filespecs.each do |spec|
         | 
| 48 | 
            +
                     progress("#{spec}: ")
         | 
| 49 | 
            +
                     Dir.glob(File.join(@rails_root,spec)) do |f|
         | 
| 50 | 
            +
                       progress('.')
         | 
| 51 | 
            +
                       collect_entries(f,@rails_root,is_erb?(f))
         | 
| 52 | 
            +
                     end
         | 
| 53 | 
            +
                     progress("\n")
         | 
| 54 | 
            +
                   end
         | 
| 55 | 
            +
                 end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                 def is_erb?(filename)
         | 
| 58 | 
            +
                   ['.erb','.rhtml'].include?(File.extname(filename))
         | 
| 59 | 
            +
                 end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                 def harvest
         | 
| 62 | 
            +
                    @new_entries = TranslationEntryCollection.new
         | 
| 63 | 
            +
                    HARVEST_SECTIONS.each_pair do |section,paths|
         | 
| 64 | 
            +
                      harvest_section(section,paths)
         | 
| 65 | 
            +
                      progress("\n")
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                    if @options[:add_paths].is_a?(Array) && @options.size > 0
         | 
| 68 | 
            +
                      harvest_section("additional paths",@options[:add_paths])
         | 
| 69 | 
            +
                      progress("\n")
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    @translation_file = ZLocalize::TranslationFile.new
         | 
| 73 | 
            +
                    unless @options[:overwrite_existing]
         | 
| 74 | 
            +
                       progress("Merging existing translations from #{@options[:output]}...\n")
         | 
| 75 | 
            +
                       begin
         | 
| 76 | 
            +
                         @translation_file.load(File.join(@rails_root,@options[:output]))
         | 
| 77 | 
            +
                       rescue
         | 
| 78 | 
            +
                       end
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    @translation_file.entries.synchronize_with(@new_entries,@options[:purge])
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    progress("Writing new translation file...\n")
         | 
| 84 | 
            +
                    File.open(File.join(@rails_root,@options[:output]),"w") do |f|
         | 
| 85 | 
            +
                       f.write(@translation_file.to_yaml)
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    progress("Done!\n\n")
         | 
| 89 | 
            +
                 end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                 def harvest_file(filename)
         | 
| 92 | 
            +
                   @new_entries = TranslationEntryCollection.new
         | 
| 93 | 
            +
                   collect_entries(filename,@rails_root,false)
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                   @translation_file = ZLocalize::TranslationFile.new
         | 
| 96 | 
            +
                   # merge
         | 
| 97 | 
            +
                   @translation_file.load(File.join(@rails_root,@options[:output]))
         | 
| 98 | 
            +
                   @translation_file.entries.synchronize_with(@new_entries,@options[:purge])
         | 
| 99 | 
            +
                   File.open(File.join(@rails_root,@options[:output]),"w") do |f|
         | 
| 100 | 
            +
                      f.write(@translation_file.to_yaml)
         | 
| 101 | 
            +
                   end
         | 
| 102 | 
            +
                 end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                 # collect entries in a source file
         | 
| 105 | 
            +
                 def collect_entries(filename,root,is_erb = false)
         | 
| 106 | 
            +
                    sp = ZLocalize::SourceParser.new(filename,root,is_erb)
         | 
| 107 | 
            +
                    sp.parse
         | 
| 108 | 
            +
                    sp.translation_entries.each do |key,te|
         | 
| 109 | 
            +
                      if @new_entries[key]
         | 
| 110 | 
            +
                         te.references.each do |ref|
         | 
| 111 | 
            +
                            @new_entries[key].add_reference(ref)
         | 
| 112 | 
            +
                         end
         | 
| 113 | 
            +
                      else
         | 
| 114 | 
            +
                        @new_entries[key] = te.dup
         | 
| 115 | 
            +
                      end
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                 end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
               end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            end # module Harvester
         | 
| 122 | 
            +
             | 
| 123 | 
            +
             | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'zlocalize/rails/attached_translations'
         | 
| 4 | 
            +
            ActiveRecord::Base.send(:include, ZLocalize::Translatable::AttachedTranslations)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'zlocalize/rails/translated_columns'
         | 
| 7 | 
            +
            ActiveRecord::Base.send(:include, ZLocalize::Translatable::TranslatedColumns)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require 'zlocalize/rails/decimal_attributes'
         | 
| 10 | 
            +
            ActiveRecord::Base.send(:include, ZLocalize::Translatable::LocalizedDecimalAttributes)
         | 
| 11 | 
            +
             | 
| @@ -0,0 +1,122 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            #  === Translation of attributes values for ActiveRecord ===
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            #  Allows a model to have multiple values (one for each given locale) stored for one or more attributes
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            #  Uses a Translation model, declared as a polymorphic association
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            #  Example use:
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
            #  class Article < ActiveRecord::Base
         | 
| 13 | 
            +
            #    ....
         | 
| 14 | 
            +
            #    has_translations
         | 
| 15 | 
            +
            #    ....
         | 
| 16 | 
            +
            #  end
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            #  > @article = Article.new
         | 
| 20 | 
            +
            #  > @article.insert_translations(:fr => { :title => "L'année en revue" }, :en => { :title => "The Year in Review" })
         | 
| 21 | 
            +
            #  > @article.translate('title','fr')
         | 
| 22 | 
            +
            #    => "L'année en revue"
         | 
| 23 | 
            +
            #  > @article.translate('title','en')
         | 
| 24 | 
            +
            #    => "The year in review"
         | 
| 25 | 
            +
            #
         | 
| 26 | 
            +
            #  It is also possible to declare the translations as nested_attributes:
         | 
| 27 | 
            +
            #
         | 
| 28 | 
            +
            #
         | 
| 29 | 
            +
            #  has_translations
         | 
| 30 | 
            +
            #  accepts_nested_attributes_for :translations, :allow_destroy => true
         | 
| 31 | 
            +
            #
         | 
| 32 | 
            +
            #  which would allow to assign translations from within a form (using fields_for helper).
         | 
| 33 | 
            +
            #
         | 
| 34 | 
            +
            #  params[:article][:translations_attributes] = [ { :locale => :fr, :name => 'title', :value => "L'année en revue" },
         | 
| 35 | 
            +
            #                                                 { :locale => :en, :name => 'title', :value => "The Year in Review" } ]
         | 
| 36 | 
            +
            #
         | 
| 37 | 
            +
            module ZLocalize
         | 
| 38 | 
            +
              module Translatable #:nodoc:
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                class TranslationError < StandardError #:nodoc:
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                module AttachedTranslations #:nodoc:
         | 
| 44 | 
            +
                  def self.included(base)
         | 
| 45 | 
            +
                    base.extend(ClassMethods)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  module ClassMethods
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def has_translations
         | 
| 51 | 
            +
                      has_many :translations, :as => :translated, :dependent => :destroy
         | 
| 52 | 
            +
                      include ZLocalize::Translatable::AttachedTranslations::InstanceMethods
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  module InstanceMethods
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def translate(attr_name,locale = nil)
         | 
| 60 | 
            +
                      locale ||= ZLocalize.locale
         | 
| 61 | 
            +
                      if tr = find_translation(attr_name,locale)
         | 
| 62 | 
            +
                        tr.value
         | 
| 63 | 
            +
                      else
         | 
| 64 | 
            +
                        ''
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    def add_translation(name,locale,value)
         | 
| 69 | 
            +
                      if tr = find_translation(name,locale)
         | 
| 70 | 
            +
                        tr.value = value.to_s
         | 
| 71 | 
            +
                      else
         | 
| 72 | 
            +
                        tr = translations.build(:translated => self, :name => name.to_s, :locale => locale.to_s, :value => value.to_s)
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                      tr
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    # convenience method to accept a Hash containing the translations for multiple columns and
         | 
| 78 | 
            +
                    # locales. +locales+ must have the locales as keys and its value is another Hash of name-value pairs.
         | 
| 79 | 
            +
                    # Example:
         | 
| 80 | 
            +
                    #    @article.insert_translations(
         | 
| 81 | 
            +
                    #         { 'en' => { 'title'    => "What's new this week",
         | 
| 82 | 
            +
                    #                     'synopsis' => "Learn what has happened this week in our little world"},
         | 
| 83 | 
            +
                    #           'fr' => { 'title'    => "Quoi de neuf cette semaine",
         | 
| 84 | 
            +
                    #                     'synopsis' => "Apprenez tout sur ce qui s'est passé cette semaine dans notre petit monde" }
         | 
| 85 | 
            +
                    #
         | 
| 86 | 
            +
                    # If you have user-generated content, for example, you can quickly
         | 
| 87 | 
            +
                    # create a form to edit the translatable content as follows:
         | 
| 88 | 
            +
                    # in the view:
         | 
| 89 | 
            +
                    #    <label>Title in English</label>
         | 
| 90 | 
            +
                    #    <%= text_field 'translations[en][title]', @article.translate('title','en') %>
         | 
| 91 | 
            +
                    #    <label>Title in French</label>
         | 
| 92 | 
            +
                    #    <%= text_field 'translations[fr][title]', @article.translate('title','fr') %>
         | 
| 93 | 
            +
                    #
         | 
| 94 | 
            +
                    # in the controller:
         | 
| 95 | 
            +
                    #
         | 
| 96 | 
            +
                    # def update
         | 
| 97 | 
            +
                    #   ...
         | 
| 98 | 
            +
                    #   @article.insert_translations(params['translations'])
         | 
| 99 | 
            +
                    #   @article.save
         | 
| 100 | 
            +
                    # end
         | 
| 101 | 
            +
                    #
         | 
| 102 | 
            +
                    # make sure your content is sanitized!
         | 
| 103 | 
            +
                    #
         | 
| 104 | 
            +
                    def insert_translations(locales = {})
         | 
| 105 | 
            +
                      Translation.transaction do
         | 
| 106 | 
            +
                        locales.each do |locale,terms|
         | 
| 107 | 
            +
                          terms.each do |name,value|
         | 
| 108 | 
            +
                            add_translation(name,locale,value)
         | 
| 109 | 
            +
                          end
         | 
| 110 | 
            +
                        end
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    protected
         | 
| 115 | 
            +
                      def find_translation(name,locale)
         | 
| 116 | 
            +
                        translations.detect { |t| (t.name == name.to_s) && (t.locale == locale.to_s) }
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
            end
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            #  == Localized Decimal Attributes ==
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # Provides a way to ensure Float attributes on ActiveRecord are converted to the internal format
         | 
| 6 | 
            +
            # used by Ruby and Rails.
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # Each locale defines a method to convert a String representing a float in a given locale into an actual Float
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # Example with :fr (French) locale:
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
            # 1) configuration (somewhere in an initializer):
         | 
| 13 | 
            +
            #
         | 
| 14 | 
            +
            #  ZLocalize.config.locales = {
         | 
| 15 | 
            +
            #  :fr => {
         | 
| 16 | 
            +
            #     :plural_select    => lambda { |n| n <= 0 ? 0 : (n > 1 ? 2 : 1) },
         | 
| 17 | 
            +
            #     .....
         | 
| 18 | 
            +
            #     :titleize         => lambda { |s| s.capitalize.to_s },
         | 
| 19 | 
            +
            #     :convert_float    => lambda { |s| s.to_s.gsub(' ','').gsub(',','.') }
         | 
| 20 | 
            +
            #  },
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
            # 2) An Order model:
         | 
| 23 | 
            +
            #
         | 
| 24 | 
            +
            #   class Order < ActiveRecord::Base
         | 
| 25 | 
            +
            #      ....
         | 
| 26 | 
            +
            #      localize_decimal_attributes [:sub_total, :taxes]
         | 
| 27 | 
            +
            #      ....
         | 
| 28 | 
            +
            #   end
         | 
| 29 | 
            +
            #
         | 
| 30 | 
            +
            #
         | 
| 31 | 
            +
            # 3) This allows to assign attributes with their French representation of a decimal number:
         | 
| 32 | 
            +
            #
         | 
| 33 | 
            +
            #  > order.attributes = {:sub_total => "1 222,54", :taxes => "1,23" }
         | 
| 34 | 
            +
            #  > order.sub_total   # => 1222.54
         | 
| 35 | 
            +
            #  > order.taxes       # => 1.23
         | 
| 36 | 
            +
            #
         | 
| 37 | 
            +
            #
         | 
| 38 | 
            +
            module ZLocalize
         | 
| 39 | 
            +
              module Translatable #:nodoc:
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                module LocalizedDecimalAttributes #:nodoc:
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def self.included(base)
         | 
| 44 | 
            +
                    base.extend(ClassMethods)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  module ClassMethods
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def localize_decimal_attributes(column_names)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      [column_names].flatten.each do |col_name|
         | 
| 52 | 
            +
                        class_eval "def #{col_name}=(value)
         | 
| 53 | 
            +
                                      write_localized_decimal_attribute('#{col_name}',value)
         | 
| 54 | 
            +
                                    end"
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      include ZLocalize::Translatable::LocalizedDecimalAttributes::InstanceMethods
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  module InstanceMethods
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def write_localized_decimal_attribute(col_name,value,locale = nil)
         | 
| 64 | 
            +
                      s = value.nil? ? nil : ZLocalize.convert_float(locale || ZLocalize.locale,value)
         | 
| 65 | 
            +
                      self.send(:write_attribute, col_name,s)
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  end # module InstanceMethods
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                end # module TranslatedColumns
         | 
| 71 | 
            +
              end # module Translatable
         | 
| 72 | 
            +
            end # module ZLocalize
         | 
| 73 | 
            +
             | 
| 74 | 
            +
             | 
| 75 | 
            +
             | 
| 76 | 
            +
             | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # -*- encoding : utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rails/generators/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # module here is deliberately named Zlocalize (as opposed to ZLocalize), for consistency
         | 
| 6 | 
            +
            # between the namespacing of rake tasks and rails generators
         | 
| 7 | 
            +
             module Zlocalize
         | 
| 8 | 
            +
                module Generators
         | 
| 9 | 
            +
                  class InitializerGenerator < Rails::Generators::Base
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    desc "Create ZLocalize initializer for your Rails application"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    source_root File.join(File.dirname(__FILE__),'templates')
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def create_initializer_file
         | 
| 16 | 
            +
                      template "initializer_template.rb", "config/initializers/zlocalize.rb"
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
               end
         | 
| 21 | 
            +
            end
         |