Commit ecd3f31242bfecf770ad76d9cb1613fc29900188

Authored by Antonio Terceiro
1 parent 6d904169

Replacing Ruby-GetText with fast_gettext

Besides being faster, consumming less memory, and being thread-safe,
fast_gettext's approach is cleaner than Ruby-GetText's because it does not
mess with the Rails internals. That's probably due to the fact that
fast_gettext was designed after Rails had proper I18N support, so that's
not exactly Ruby-GetText's fault. Current versions of Ruby-GetText are
claimed to be thread-safe as well, but I decided to go with fast_gettext
regardless.

I am messing with the Rails internals myself by copying some code from
Ruby-Gettext, but that code will be dropped when we upgrade to a more
recent Rails version with proper I18N. Code was copied from Ruby-GetText
to implement:

    * per-language cache
    * validation error messages translation

During initialization, the needed .mo files installed system-wide are
symlinked locally.  By doing this we can take "similar" locales locally
since fast_gettext does not seem to support loading of files from similar
locales (e.g.  loading pt_BR/LC_MESSAGES/domain.mo when
pt/LC_MESSAGES/domain.mo is not available).

This hopefully will fix the long-standing bug with messed up translations
due to high concurrency and non-thread-safety of the version of
Ruby-GetText in Debian Lenny.

(ActionItem1315)
app/controllers/application.rb
@@ -70,20 +70,15 @@ class ApplicationController < ActionController::Base @@ -70,20 +70,15 @@ class ApplicationController < ActionController::Base
70 end 70 end
71 end 71 end
72 72
73 - include GetText  
74 - before_init_gettext :maybe_save_locale, :default_locale  
75 - def maybe_save_locale 73 + before_filter :set_locale
  74 + def set_locale
  75 + FastGettext.available_locales = Noosfero.available_locales
  76 + FastGettext.default_locale = Noosfero.default_locale
  77 + FastGettext.set_locale(params[:lang] || session[:lang] || Noosfero.default_locale || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
76 if params[:lang] 78 if params[:lang]
77 - cookies[:lang] = params[:lang] 79 + session[:lang] = params[:lang]
78 end 80 end
79 end 81 end
80 - def default_locale  
81 - if Noosfero.default_locale && cookies[:lang].blank?  
82 - cookies[:lang] = params[:lang] = Noosfero.default_locale  
83 - end  
84 - end  
85 - protected :maybe_save_locale, :default_locale  
86 - init_gettext 'noosfero'  
87 82
88 include NeedsProfile 83 include NeedsProfile
89 84
app/helpers/account_helper.rb
1 module AccountHelper 1 module AccountHelper
2 2
3 - include GetText  
4 3
5 def button_to_step(type, step, current_step, html_options = {}) 4 def button_to_step(type, step, current_step, html_options = {})
6 if current_step == step 5 if current_step == step
app/helpers/application_helper.rb
@@ -26,6 +26,10 @@ module ApplicationHelper @@ -26,6 +26,10 @@ module ApplicationHelper
26 26
27 include AccountHelper 27 include AccountHelper
28 28
  29 + def locale
  30 + FastGettext.locale
  31 + end
  32 +
29 def load_web2_conf 33 def load_web2_conf
30 if File.exists?( RAILS_ROOT + '/config/web2.0.yml') 34 if File.exists?( RAILS_ROOT + '/config/web2.0.yml')
31 YAML.load_file( RAILS_ROOT + '/config/web2.0.yml' ) 35 YAML.load_file( RAILS_ROOT + '/config/web2.0.yml' )
@@ -664,7 +668,6 @@ module ApplicationHelper @@ -664,7 +668,6 @@ module ApplicationHelper
664 668
665 # Should be on the forms_helper file but when its there the translation of labels doesn't work 669 # Should be on the forms_helper file but when its there the translation of labels doesn't work
666 class NoosferoFormBuilder < ActionView::Helpers::FormBuilder 670 class NoosferoFormBuilder < ActionView::Helpers::FormBuilder
667 - include GetText  
668 extend ActionView::Helpers::TagHelper 671 extend ActionView::Helpers::TagHelper
669 672
670 def self.output_field(text, field_html, field_id = nil) 673 def self.output_field(text, field_html, field_id = nil)
@@ -687,13 +690,7 @@ module ApplicationHelper @@ -687,13 +690,7 @@ module ApplicationHelper
687 (field_helpers - %w(hidden_field)).each do |selector| 690 (field_helpers - %w(hidden_field)).each do |selector|
688 src = <<-END_SRC 691 src = <<-END_SRC
689 def #{selector}(field, *args, &proc) 692 def #{selector}(field, *args, &proc)
690 - column = object.class.columns_hash[field.to_s]  
691 - text =  
692 - ( column ?  
693 - column.human_name :  
694 - field.to_s.humanize  
695 - )  
696 - 693 + text = object.class.human_attribute_name(field.to_s)
697 NoosferoFormBuilder::output_field(text, super) 694 NoosferoFormBuilder::output_field(text, super)
698 end 695 end
699 END_SRC 696 END_SRC
app/helpers/categories_helper.rb
1 module CategoriesHelper 1 module CategoriesHelper
2 2
3 - include GetText  
4 3
5 COLORS = [ 4 COLORS = [
6 [ N_('Do not display at the menu'), nil ], 5 [ N_('Do not display at the menu'), nil ],
app/helpers/content_viewer_helper.rb
1 module ContentViewerHelper 1 module ContentViewerHelper
2 2
3 - include GetText  
4 include BlogHelper 3 include BlogHelper
5 4
6 def number_of_comments(article) 5 def number_of_comments(article)
app/helpers/countries_helper.rb
@@ -2,9 +2,6 @@ class CountriesHelper @@ -2,9 +2,6 @@ class CountriesHelper
2 2
3 include Singleton 3 include Singleton
4 4
5 - include GetText  
6 - bindtextdomain 'iso_3166'  
7 -  
8 # a dump of iso_3166.xml from Debian source package iso-codes 5 # a dump of iso_3166.xml from Debian source package iso-codes
9 COUNTRIES = [ 6 COUNTRIES = [
10 ["Afghanistan", "AF"], 7 ["Afghanistan", "AF"],
app/helpers/dates_helper.rb
1 -module DatesHelper 1 +require 'noosfero/i18n'
2 2
3 - include GetText 3 +module DatesHelper
4 4
5 # FIXME Date#strftime should translate this for us !!!! 5 # FIXME Date#strftime should translate this for us !!!!
6 MONTHS = [ 6 MONTHS = [
app/helpers/language_helper.rb
1 module LanguageHelper 1 module LanguageHelper
2 def language 2 def language
3 - if Noosfero.available_locales.include?(locale.to_s) ||  
4 - Noosfero.available_locales.include?(locale.language)  
5 - locale.language  
6 - else  
7 - Noosfero.default_locale || 'en'  
8 - end 3 + locale
9 end 4 end
10 5
11 def tinymce_language 6 def tinymce_language
app/helpers/profile_editor_helper.rb
1 module ProfileEditorHelper 1 module ProfileEditorHelper
2 2
3 - include GetText  
4 3
5 AREAS_OF_STUDY = [ 4 AREAS_OF_STUDY = [
6 N_('Agrometeorology'), 5 N_('Agrometeorology'),
app/models/task.rb
@@ -12,7 +12,6 @@ @@ -12,7 +12,6 @@
12 class Task < ActiveRecord::Base 12 class Task < ActiveRecord::Base
13 13
14 module Status 14 module Status
15 - include GetText  
16 # the status of tasks just created 15 # the status of tasks just created
17 ACTIVE = 1 16 ACTIVE = 1
18 17
app/views/layouts/application-ng.rhtml
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 } 37 }
38 </script> 38 </script>
39 39
40 - <a href="#content" id="link-go-content"><span>Ir para o conteúdo</span></a> 40 + <a href="#content" id="link-go-content"><span><%= _("Go to the content") %></span></a>
41 41
42 <div id="wrap-1"> 42 <div id="wrap-1">
43 <div id='theme-header'> 43 <div id='theme-header'>
config/environment.rb
@@ -10,17 +10,6 @@ RAILS_GEM_VERSION = &#39;2.1.0&#39; unless defined? RAILS_GEM_VERSION @@ -10,17 +10,6 @@ RAILS_GEM_VERSION = &#39;2.1.0&#39; unless defined? RAILS_GEM_VERSION
10 # Bootstrap the Rails environment, frameworks, and default configuration 10 # Bootstrap the Rails environment, frameworks, and default configuration
11 require File.join(File.dirname(__FILE__), 'boot') 11 require File.join(File.dirname(__FILE__), 'boot')
12 12
13 -# locally-developed modules  
14 -require 'locale'  
15 -require 'gettext/rails'  
16 -require 'acts_as_filesystem'  
17 -require 'acts_as_having_settings'  
18 -require 'acts_as_searchable'  
19 -require 'acts_as_having_boxes'  
20 -require 'acts_as_having_image'  
21 -require 'will_paginate'  
22 -require 'route_if'  
23 -  
24 # extra directories for controllers organization 13 # extra directories for controllers organization
25 extra_controller_dirs = %w[ 14 extra_controller_dirs = %w[
26 app/controllers/my_profile 15 app/controllers/my_profile
config/initializers/dependencies.rb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +# locally-developed modules
  2 +require 'acts_as_filesystem'
  3 +require 'acts_as_having_settings'
  4 +require 'acts_as_searchable'
  5 +require 'acts_as_having_boxes'
  6 +require 'acts_as_having_image'
  7 +require 'route_if'
  8 +
  9 +# third-party libraries
  10 +require 'will_paginate'
config/initializers/fast_gettext.rb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +require 'noosfero/i18n'
features/step_definitions/internationalization_steps.rb
1 -def language_to_header(name) 1 +def language_to_code(name)
2 { 2 {
3 'Brazilian Portuguese' => 'pt-br', 3 'Brazilian Portuguese' => 'pt-br',
4 'European Portuguese' => 'pt-pt', 4 'European Portuguese' => 'pt-pt',
@@ -17,29 +17,25 @@ def native_name(name) @@ -17,29 +17,25 @@ def native_name(name)
17 }[name] || name 17 }[name] || name
18 end 18 end
19 19
20 -def language_to_code(name)  
21 - language_to_header(name)  
22 -end  
23 -  
24 Given /^Noosfero is configured to use (.+) as default$/ do |lang| 20 Given /^Noosfero is configured to use (.+) as default$/ do |lang|
25 Noosfero.default_locale = language_to_code(lang) 21 Noosfero.default_locale = language_to_code(lang)
26 end 22 end
27 23
28 -After('@default_locale_config') do 24 +After do
  25 + # reset everything back to normal
29 Noosfero.default_locale = nil 26 Noosfero.default_locale = nil
  27 + FastGettext.locale = 'en'
30 end 28 end
31 29
32 Given /^a user accessed in (.*) before$/ do |lang| 30 Given /^a user accessed in (.*) before$/ do |lang|
33 session = Webrat::Session.new(Webrat.adapter_class.new(self)) 31 session = Webrat::Session.new(Webrat.adapter_class.new(self))
34 session.extend(Webrat::Matchers) 32 session.extend(Webrat::Matchers)
35 session.visit('/') 33 session.visit('/')
36 - session.should have_selector('html[lang=en]') 34 + session.should have_selector("html[lang=#{language_to_code(lang)}]")
37 end 35 end
38 36
39 Given /^my browser prefers (.*)$/ do |lang| 37 Given /^my browser prefers (.*)$/ do |lang|
40 - @n ||= 0  
41 - header 'Accept-Language', language_to_header(lang)  
42 - 38 + header 'Accept-Language', language_to_code(lang)
43 end 39 end
44 40
45 Then /^the site should be in (.*)$/ do |lang| 41 Then /^the site should be in (.*)$/ do |lang|
lib/noosfero.rb
@@ -12,12 +12,15 @@ module Noosfero @@ -12,12 +12,15 @@ module Noosfero
12 attr_accessor :locales 12 attr_accessor :locales
13 attr_accessor :default_locale 13 attr_accessor :default_locale
14 def available_locales 14 def available_locales
15 - @available_locales ||= locales.keys  
16 - end  
17 - def each_locale  
18 - locales.keys.sort.each do |key|  
19 - yield(key, locales[key])  
20 - end 15 + @available_locales ||=
  16 + begin
  17 + locales_list = locales.keys
  18 + # move English to the beginning
  19 + if locales_list.include?('en')
  20 + locales_list = ['en'] + (locales_list - ['en']).sort
  21 + end
  22 + locales_list
  23 + end
21 end 24 end
22 end 25 end
23 26
lib/noosfero/i18n.rb 0 → 100644
@@ -0,0 +1,183 @@ @@ -0,0 +1,183 @@
  1 +require 'fast_gettext'
  2 +
  3 +class Object
  4 + include FastGettext::Translation
  5 + alias :gettext :_
  6 + alias :ngettext :n_
  7 +end
  8 +
  9 +class ActiveRecord::Errors
  10 + default_error_messages.update(
  11 + :inclusion => N_("%{fn} is not included in the list"),
  12 + :exclusion => N_("%{fn} is reserved"),
  13 + :invalid => N_("%{fn} is invalid"),
  14 + :confirmation => N_("%{fn} doesn't match confirmation"),
  15 + :accepted => N_("%{fn} must be accepted"),
  16 + :empty => N_("%{fn} can't be empty"),
  17 + :blank => N_("%{fn} can't be blank"),
  18 + :too_long => N_("%{fn} is too long (maximum is %d characters)"),
  19 + :too_short => N_("%{fn} is too short (minimum is %d characters)"),
  20 + :wrong_length => N_("%{fn} is the wrong length (should be %d characters)"),
  21 + :taken => N_("%{fn} has already been taken"),
  22 + :not_a_number => N_("%{fn} is not a number")
  23 + )
  24 +
  25 + def localize_error_messages
  26 + errors = {}
  27 + each do |attr,msg|
  28 + next if msg.nil?
  29 + errors[attr] ||= []
  30 + errors[attr] << _(msg).sub('%{fn}', @base.class.human_attribute_name(attr))
  31 + end
  32 + errors
  33 + end
  34 + def on_with_gettext(attribute)
  35 + errors = localize_error_messages[attribute.to_s]
  36 + return nil if errors.nil?
  37 + errors.size == 1 ? errors.first : errors
  38 + end
  39 + alias_method_chain :on, :gettext
  40 +
  41 + def full_messages_with_gettext
  42 + full_messages = []
  43 + errors = localize_error_messages
  44 + errors.each_key do |attr|
  45 + errors[attr].each do |msg|
  46 + next if msg.nil?
  47 + full_messages << msg
  48 + end
  49 + end
  50 + full_messages
  51 + end
  52 + alias_method_chain :full_messages, :gettext
  53 +end
  54 +
  55 +
  56 +module ActionView::Helpers::ActiveRecordHelper
  57 + module L10n
  58 + @error_message_title = Nn_("%{num} error prohibited this %{record} from being saved", "%{num} errors prohibited this %{record} from being saved").flatten
  59 + @error_message_explanation = Nn_("There was a problem with the following field:", "There were problems with the following fields:").flatten
  60 + module_function
  61 + def error_messages_for(instance, objects, object_names, count, options)
  62 + record = _(options[:object_name] || object_names[0].to_s)
  63 +
  64 + html = {}
  65 + [:id, :class].each do |key|
  66 + if options.include?(key)
  67 + value = options[key]
  68 + html[key] = value unless value.blank?
  69 + else
  70 + html[key] = 'errorExplanation'
  71 + end
  72 + end
  73 +
  74 + if options[:message_title]
  75 + header_message = instance.error_message(options[:message_title], count) % {:num => count, :record => record}
  76 + else
  77 + header_message = ((count == 1) ? _(@error_message_title[0]) : _(@error_message_title[1])) % {:num => count, :record => record}
  78 + end
  79 + if options[:message_explanation]
  80 + message_explanation = instance.error_message(options[:message_explanation], count) % {:num => count}
  81 + else
  82 + message_explanation = (count == 1 ? _(@error_message_explanation[0]) : _(@error_message_explanation[1])) % {:num => count}
  83 + end
  84 +
  85 + error_messages = objects.map {|object| object.errors.full_messages.map {|msg| instance.content_tag(:li, msg) } }
  86 +
  87 + instance.content_tag(
  88 + :div,
  89 + instance.content_tag(options[:header_tag] || :h2, header_message) <<
  90 + instance.content_tag(:p, message_explanation) <<
  91 + instance.content_tag(:ul, error_messages),
  92 + html
  93 + )
  94 + end
  95 + end
  96 +
  97 + alias error_messages_for_without_localize error_messages_for #:nodoc:
  98 +
  99 + # error_messages_for overrides original method with localization.
  100 + # And also it extends to be able to replace the title/explanation of the header of the error dialog. (Since 1.90)
  101 + # If you want to override these messages in the whole application,
  102 + # use ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_(title|explanation) instead.
  103 + # * :message_title - the title of message. Use Nn_() to path the strings for singular/plural.
  104 + # e.g. Nn_("%{num} error prohibited this %{record} from being saved",
  105 + # "%{num} errors prohibited this %{record} from being saved")
  106 + # * :message_explanation - the explanation of message
  107 + # e.g. Nn_("There was a problem with the following field:",
  108 + # "There were %{num} problems with the following fields:")
  109 + def error_messages_for(*params)
  110 + options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
  111 + objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
  112 + object_names = params.dup
  113 + count = objects.inject(0) {|sum, object| sum + object.errors.count }
  114 + if count.zero?
  115 + ''
  116 + else
  117 + L10n.error_messages_for(self, objects, object_names, count, options)
  118 + end
  119 + end
  120 +
  121 +end
  122 +
  123 +module ActionController::Caching::Fragments
  124 + def fragment_cache_key_with_fast_gettext(name)
  125 + ret = fragment_cache_key_without_fast_gettext(name)
  126 + if ret.is_a? String
  127 + ret.gsub(/:/, ".") << "_#{FastGettext.locale}"
  128 + else
  129 + ret
  130 + end
  131 + end
  132 + alias_method_chain :fragment_cache_key, :fast_gettext
  133 +
  134 + def expire_fragment_with_fast_gettext(name, options = nil)
  135 + return unless perform_caching
  136 +
  137 + key = fragment_cache_key_without_fast_gettext(name)
  138 + if key.is_a?(Regexp)
  139 + self.class.benchmark "Expired fragments matching: #{key.source}" do
  140 + fragment_cache_store.delete_matched(key, options)
  141 + end
  142 + else
  143 + key = key.gsub(/:/, ".")
  144 + self.class.benchmark "Expired fragment: #{key}, lang = #{FastGettext.available_locales.inspect}" do
  145 + if FastGettext.available_locales
  146 + FastGettext.available_locales.each do |lang|
  147 + fragment_cache_store.delete("#{key}_#{lang}", options)
  148 + end
  149 + end
  150 + end
  151 + end
  152 + end
  153 + alias_method_chain :expire_fragment, :fast_gettext
  154 +end
  155 +
  156 +FileUtils.mkdir_p(Rails.root + '/locale')
  157 +Dir.glob(Rails.root + '/locale/*').each do |dir|
  158 + lang = File.basename(dir)
  159 + FileUtils.mkdir_p("#{Rails.root}/locale/#{lang}/LC_MESSAGES")
  160 + ['iso_3166', 'rails'].each do |domain|
  161 + target = "#{Rails.root}/locale/#{lang}/LC_MESSAGES/#{domain}.mo"
  162 + if !File.exists?(target)
  163 + orig = "/usr/share/locale/#{lang}/LC_MESSAGES/#{domain}.mo"
  164 + if File.exists?(orig)
  165 + File.symlink(orig, target)
  166 + else
  167 + alternatives = Dir.glob("/usr/share/locale/#{lang}_*/LC_MESSAGES/#{domain}.mo")
  168 + unless alternatives.empty?
  169 + File.symlink(alternatives.first, target)
  170 + end
  171 + end
  172 + end
  173 + end
  174 +end
  175 +
  176 +repos = [
  177 + FastGettext::TranslationRepository.build('noosfero', :type => 'mo', :path => Rails.root + '/locale'),
  178 + FastGettext::TranslationRepository.build('iso_3166', :type => 'mo', :path => Rails.root + '/locale'),
  179 + FastGettext::TranslationRepository.build('rails', :type => 'mo', :path => Rails.root + '/locale'),
  180 +]
  181 +
  182 +FastGettext.add_text_domain 'noosferofull', :type => :chain, :chain => repos
  183 +FastGettext.default_text_domain = 'noosferofull'
lib/tasks/populate.rake
1 require File.dirname(__FILE__) + '/../../config/environment' 1 require File.dirname(__FILE__) + '/../../config/environment'
2 require 'noosfero' 2 require 'noosfero'
3 -require 'gettext/rails'  
4 -include GetText  
5 3
6 DEFAULT_ENVIRONMENT_TEXT = <<EOF 4 DEFAULT_ENVIRONMENT_TEXT = <<EOF
7 <h1>Environment homepage</h1> 5 <h1>Environment homepage</h1>
lib/unifreire_terminology.rb
1 require 'noosfero/terminology' 1 require 'noosfero/terminology'
2 2
3 class UnifreireTerminology < Noosfero::Terminology::Custom 3 class UnifreireTerminology < Noosfero::Terminology::Custom
4 - include GetText  
5 4
6 def initialize 5 def initialize
7 # NOTE: the hash values must be marked for translation!! 6 # NOTE: the hash values must be marked for translation!!
lib/zen3_terminology.rb
1 require 'noosfero/terminology' 1 require 'noosfero/terminology'
2 2
3 class Zen3Terminology < Noosfero::Terminology::Custom 3 class Zen3Terminology < Noosfero::Terminology::Custom
4 - include GetText  
5 4
6 def initialize 5 def initialize
7 # NOTE: the hash values must be marked for translation!! 6 # NOTE: the hash values must be marked for translation!!
test/unit/communities_block_test.rb
@@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2
3 class CommunitiesBlockTest < Test::Unit::TestCase 3 class CommunitiesBlockTest < Test::Unit::TestCase
4 4
5 - include GetText  
6 -  
7 should 'inherit from ProfileListBlock' do 5 should 'inherit from ProfileListBlock' do
8 assert_kind_of ProfileListBlock, CommunitiesBlock.new 6 assert_kind_of ProfileListBlock, CommunitiesBlock.new
9 end 7 end
test/unit/enterprises_block_test.rb
@@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2
3 class EnterprisesBlockTest < Test::Unit::TestCase 3 class EnterprisesBlockTest < Test::Unit::TestCase
4 4
5 - include GetText  
6 -  
7 should 'inherit from ProfileListBlock' do 5 should 'inherit from ProfileListBlock' do
8 assert_kind_of ProfileListBlock, EnterprisesBlock.new 6 assert_kind_of ProfileListBlock, EnterprisesBlock.new
9 end 7 end
test/unit/friends_block_test.rb
@@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -2,8 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2
3 class FriendsBlockTest < ActiveSupport::TestCase 3 class FriendsBlockTest < ActiveSupport::TestCase
4 4
5 - include GetText  
6 -  
7 should 'describe itself' do 5 should 'describe itself' do
8 assert_not_equal ProfileListBlock.description, FriendsBlock.description 6 assert_not_equal ProfileListBlock.description, FriendsBlock.description
9 end 7 end
test/unit/language_helper_test.rb
@@ -4,14 +4,9 @@ class LanguageHelperTest &lt; Test::Unit::TestCase @@ -4,14 +4,9 @@ class LanguageHelperTest &lt; Test::Unit::TestCase
4 4
5 include LanguageHelper 5 include LanguageHelper
6 6
7 -  
8 should 'return current language' do 7 should 'return current language' do
9 - locale = mock  
10 - locale.stubs(:to_s).returns('pt_BR')  
11 - locale.stubs(:language).returns('pt')  
12 - stubs(:locale).returns(locale)  
13 -  
14 - assert_equal 'pt', self.language 8 + expects(:locale).returns('pt')
  9 + assert_equal 'pt', language
15 end 10 end
16 11
17 should 'remove country code for TinyMCE' do 12 should 'remove country code for TinyMCE' do