Permalink
Browse files

Ability to customize localization

- `localized_string` uses a configurable class
to perform lookups
- FormBuilder.i18n_localizer is Formtastic::Localizer
by default; this maintains backwards compatability
- Updated documentation and generator accordingly
  • Loading branch information...
1 parent dbb6e92 commit 0514d73e22a893d9e2f9b21a38f98f6e9e12962f Nathan Long committed Oct 11, 2011
View
@@ -509,6 +509,13 @@ For more flexible forms; Formtastic finds translations using a bottom-up approac
Values for @labels@/@hints@/@actions@ are can take values: @String@ (explicit value), @Symbol@ (i18n-lookup-key relative to the current "type", e.g. actions:), @true@ (force I18n lookup), @false@ (force no I18n lookup). Titles (legends) can only take: @String@ and @Symbol@ - true/false have no meaning.
+h3. Customized Localization
+
+If you want to customize your localization even further, create your own localizer class with a @localize@ method (see @Formtastic::Localizer@ for an example), and in @config/initializers/formtastic.rb@, add a line like this:
+
+<pre>
+ configure :i18n_localizer, MyApp::MyLocalizerClass
+</pre>
h2. Semantic errors
View
@@ -12,6 +12,7 @@ module Formtastic
autoload :I18n
autoload :Inputs
autoload :LocalizedString
+ autoload :Localizer
autoload :Util
# @private
@@ -23,6 +23,7 @@ def self.configure(name, value = nil)
configure :file_metadata_suffixes, ['content_type', 'file_name', 'file_size']
configure :priority_countries, ["Australia", "Canada", "United Kingdom", "United States"]
configure :i18n_lookups_by_default, true
+ configure :i18n_localizer, Formtastic::Localizer
configure :escape_html_entities_in_hints_and_labels, true
configure :default_commit_button_accesskey
configure :default_inline_error_class, 'inline-errors'
@@ -1,105 +1,17 @@
module Formtastic
- # @private
module LocalizedString
- protected
-
- # Internal generic method for looking up localized values within Formtastic
- # using I18n, if no explicit value is set and I18n-lookups are enabled.
- #
- # Enabled/Disable this by setting:
- #
- # Formtastic::FormBuilder.i18n_lookups_by_default = true/false
- #
- # Lookup priority:
- #
- # 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
- # 'formtastic.%{type}.%{model}.%{attribute}'
- # 'formtastic.%{type}.%{attribute}'
- #
- # Example:
- #
- # 'formtastic.labels.post.edit.title'
- # 'formtastic.labels.post.title'
- # 'formtastic.labels.title'
- #
- # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
- #
- def localized_string(key, value, type, options = {}) #:nodoc:
- key = value if value.is_a?(::Symbol)
-
- if value.is_a?(::String)
- escape_html_entities(value)
- else
- use_i18n = value.nil? ? i18n_lookups_by_default : (value != false)
-
- if use_i18n
- model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
-
- action_name = template.params[:action].to_s rescue ''
- attribute_name = key.to_s
-
- defaults = Formtastic::I18n::SCOPES.reject do |i18n_scope|
- nested_model_name.nil? && i18n_scope.match(/nested_model/)
- end.collect do |i18n_scope|
- i18n_path = i18n_scope.dup
- i18n_path.gsub!('%{action}', action_name)
- i18n_path.gsub!('%{model}', model_name)
- i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
- i18n_path.gsub!('%{attribute}', attribute_name)
- i18n_path.gsub!('..', '.')
- i18n_path.to_sym
- end
- defaults << ''
-
- defaults.uniq!
-
- default_key = defaults.shift
- i18n_value = Formtastic::I18n.t(default_key,
- options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
- i18n_value = i18n_value.is_a?(::String) ? i18n_value : nil
- if i18n_value.blank? && type == :label
- # This is effectively what Rails label helper does for i18n lookup
- options[:scope] = [:helpers, type]
- options[:default] = defaults
- i18n_value = ::I18n.t(default_key, options)
- end
- (i18n_value.is_a?(::String) && i18n_value.present?) ? escape_html_entities(i18n_value) : nil
- end
- end
- end
-
def model_name
@object.present? ? @object.class.name : @object_name.to_s.classify
end
- def normalize_model_name(name)
- if !name =~ /\[/ && self.respond_to?(:builder) && builder.respond_to?(:parent_builder) && builder.parent_builder.object_name
- # Rails 3.1 nested builder case
- [builder.parent_builder.object_name.to_s, name]
- elsif name =~ /(.+)\[(.+)\]/
- # Rails 3 (and 3.1?) nested builder case with :post rather than @post
- [$1, $2]
- elsif self.respond_to?(:builder) && builder.respond_to?(:options) && builder.options.key?(:parent_builder)
- # Rails 3.0 nested builder work-around case, where :parent_builder is provided by f.semantic_form_for
- [builder.options[:parent_builder].object_name.to_s, name]
- else
- # Non-nested case
- [name]
- end
- end
-
- def escape_html_entities(string) #:nodoc:
- if (respond_to?(:builder) && builder.escape_html_entities_in_hints_and_labels) ||
- (self.respond_to?(:escape_html_entities_in_hints_and_labels) && escape_html_entities_in_hints_and_labels)
- string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true # Acceppt html_safe flag as indicator to skip escaping
- end
- string
- end
+ protected
- def i18n_lookups_by_default
- respond_to?(:builder) ? builder.i18n_lookups_by_default : i18n_lookups_by_default
+ def localized_string(key, value, type, options = {}) #:nodoc:
+ current_builder = respond_to?(:builder) ? builder : self
+ localizer = Formtastic::FormBuilder.i18n_localizer.new(current_builder)
+ localizer.localize(key, value, type, options)
end
end
-end
+end
View
@@ -0,0 +1,107 @@
+module Formtastic
+ class Localizer
+
+ attr_accessor :builder
+
+ def initialize(current_builder)
+ self.builder = current_builder
+ end
+
+ # Internal generic method for looking up localized values within Formtastic
+ # using I18n, if no explicit value is set and I18n-lookups are enabled.
+ #
+ # Enabled/Disable this by setting:
+ #
+ # Formtastic::FormBuilder.i18n_lookups_by_default = true/false
+ #
+ # Lookup priority:
+ #
+ # 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
+ # 'formtastic.%{type}.%{model}.%{attribute}'
+ # 'formtastic.%{type}.%{attribute}'
+ #
+ # Example:
+ #
+ # 'formtastic.labels.post.edit.title'
+ # 'formtastic.labels.post.title'
+ # 'formtastic.labels.title'
+ #
+ # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
+ #
+ def localize(key, value, type, options = {}) #:nodoc:
+ key = value if value.is_a?(::Symbol)
+
+ if value.is_a?(::String)
+ escape_html_entities(value)
+ else
+ use_i18n = value.nil? ? i18n_lookups_by_default : (value != false)
+
+ if use_i18n
+ model_name, nested_model_name = normalize_model_name(builder.model_name.underscore)
+
+ action_name = builder.template.params[:action].to_s rescue ''
+ attribute_name = key.to_s
+
+
+ defaults = Formtastic::I18n::SCOPES.reject do |i18n_scope|
+ nested_model_name.nil? && i18n_scope.match(/nested_model/)
+ end.collect do |i18n_scope|
+ i18n_path = i18n_scope.dup
+ i18n_path.gsub!('%{action}', action_name)
+ i18n_path.gsub!('%{model}', model_name)
+ i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
+ i18n_path.gsub!('%{attribute}', attribute_name)
+ i18n_path.gsub!('..', '.')
+ i18n_path.to_sym
+ end
+ defaults << ''
+
+ defaults.uniq!
+
+ default_key = defaults.shift
+ i18n_value = Formtastic::I18n.t(default_key,
+ options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
+ i18n_value = i18n_value.is_a?(::String) ? i18n_value : nil
+ if i18n_value.blank? && type == :label
+ # This is effectively what Rails label helper does for i18n lookup
+ options[:scope] = [:helpers, type]
+ options[:default] = defaults
+ i18n_value = ::I18n.t(default_key, options)
+ end
+ (i18n_value.is_a?(::String) && i18n_value.present?) ? escape_html_entities(i18n_value) : nil
+ end
+ end
+ end
+
+ protected
+
+ def normalize_model_name(name)
+ if !name =~ /\[/ && builder.respond_to?(:parent_builder) && builder.parent_builder.object_name
+ # Rails 3.1 nested builder case
+ [builder.parent_builder.object_name.to_s, name]
+ elsif name =~ /(.+)\[(.+)\]/
+ # Rails 3 (and 3.1?) nested builder case with :post rather than @post
+ [$1, $2]
+ elsif builder.respond_to?(:options) && builder.options.key?(:parent_builder)
+ # Rails 3.0 nested builder work-around case, where :parent_builder is provided by f.semantic_form_for
+ [builder.options[:parent_builder].object_name.to_s, name]
+ else
+ # Non-nested case
+ [name]
+ end
+ end
+
+ def escape_html_entities(string) #:nodoc:
+ if (builder.escape_html_entities_in_hints_and_labels) ||
+ (self.respond_to?(:escape_html_entities_in_hints_and_labels) && escape_html_entities_in_hints_and_labels)
+ string = builder.template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true # Accept html_safe flag as indicator to skip escaping
+ end
+ string
+ end
+
+ def i18n_lookups_by_default
+ builder.i18n_lookups_by_default
+ end
+
+ end
+end
@@ -68,6 +68,12 @@
# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
# Formtastic::FormBuilder.i18n_lookups_by_default = false
+# Specifies the class to use for localization lookups. You can create your own
+# class and use it instead. It must have a `localize` method - see
+# `Formtastic::Localizer` for details. (You may want to inherit from that
+# class to call `super`.)
+# Formtastic::FormBuilder.i18n_localizer = Formtastic::Localizer
+
# You can add custom inputs or override parts of Formtastic by subclassing Formtastic::FormBuilder and
# specifying that class here. Defaults to Formtastic::FormBuilder.
# Formtastic::Helpers::FormHelper.builder = MyCustomBuilder

0 comments on commit 0514d73

Please sign in to comment.