From 30f953f3f0f83d0071886cf6ff464e50a86bf3ca Mon Sep 17 00:00:00 2001
From: Caio SBA <caiosba@gmail.com>
Date: Thu, 9 Dec 2010 03:38:22 -0200
Subject: [PATCH] Adding articles translation support

(ActionItem894)
---
 app/controllers/my_profile/cms_controller.rb       |    9 +
 .../public/content_viewer_controller.rb            |   26 +++
 app/helpers/application_helper.rb                  |    2 +-
 app/helpers/content_viewer_helper.rb               |   12 ++
 app/models/article.rb                              |   67 ++++++++
 app/models/blog.rb                                 |    4 +
 app/models/blog_archives_block.rb                  |    2 +-
 app/models/environment.rb                          |    2 +-
 app/models/event.rb                                |    4 +
 app/models/rss_feed.rb                             |    3 +-
 app/models/textile_article.rb                      |    4 +
 app/models/tiny_mce_article.rb                     |    4 +
 app/views/cms/_blog.rhtml                          |    3 +
 app/views/cms/_event.rhtml                         |    2 +
 app/views/cms/_textile_article.rhtml               |    2 +
 app/views/cms/_tiny_mce_article.rhtml              |    2 +
 app/views/cms/_translatable.rhtml                  |    7 +
 app/views/content_viewer/view_page.rhtml           |    6 +
 ...dd_language_and_translation_of_id_to_article.rb |   29 ++++
 features/edit_article.feature                      |   29 ++++
 public/designs/icons/tango/style.css               |    1 +
 public/stylesheets/application.css                 |   43 +++++
 test/functional/application_controller_test.rb     |   22 +++
 test/functional/cms_controller_test.rb             |   59 +++++++
 test/functional/content_viewer_controller_test.rb  |  143 ++++++++++++++++
 test/unit/article_test.rb                          |  170 ++++++++++++++++++++
 test/unit/blog_archives_block_test.rb              |   32 ++++
 test/unit/blog_test.rb                             |   16 ++
 test/unit/event_test.rb                            |    6 +
 test/unit/rss_feed_test.rb                         |   20 +++
 test/unit/textile_article_test.rb                  |    5 +
 test/unit/tiny_mce_article_test.rb                 |    5 +-
 32 files changed, 736 insertions(+), 5 deletions(-)
 create mode 100644 app/views/cms/_translatable.rhtml
 create mode 100644 db/migrate/20101205034144_add_language_and_translation_of_id_to_article.rb

diff --git a/app/controllers/my_profile/cms_controller.rb b/app/controllers/my_profile/cms_controller.rb
index 1feb652..e7b9495 100644
--- a/app/controllers/my_profile/cms_controller.rb
+++ b/app/controllers/my_profile/cms_controller.rb
@@ -88,6 +88,7 @@ class CmsController < MyProfileController
     @article = profile.articles.find(params[:id])
     @parent_id = params[:parent_id]
     @type = params[:type] || @article.class.to_s
+    translations if @article.translatable?
     continue = params[:continue]
 
     refuse_blocks
@@ -137,6 +138,8 @@ class CmsController < MyProfileController
       @parent_id = parent.id
     end
 
+    translations if @article.translatable?
+
     @article.profile = profile
     @article.last_changed_by = user
 
@@ -354,5 +357,11 @@ class CmsController < MyProfileController
   def per_page
     10
   end
+
+  def translations
+    @locales = Noosfero.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
+    @selected_locale = @article.language || FastGettext.locale
+  end
+
 end
 
diff --git a/app/controllers/public/content_viewer_controller.rb b/app/controllers/public/content_viewer_controller.rb
index 56d93da..7c74a12 100644
--- a/app/controllers/public/content_viewer_controller.rb
+++ b/app/controllers/public/content_viewer_controller.rb
@@ -51,6 +51,8 @@ class ContentViewerController < ApplicationController
       return
     end
 
+    redirect_to_translation
+
     # At this point the page will be showed
     @page.hit
 
@@ -85,7 +87,11 @@ class ContentViewerController < ApplicationController
         @page.posts
       end
 
+      posts = posts.native_translations if @page.blog? && @page.display_posts_in_current_language?
+
       @posts = posts.paginate({ :page => params[:npage], :per_page => @page.posts_per_page }.merge(Article.display_filter(user, profile)))
+
+      @posts.map!{ |p| p.get_translation_to(FastGettext.locale) } if @page.blog? && @page.display_posts_in_current_language?
     end
 
     if @page.folder? && @page.gallery?
@@ -125,4 +131,24 @@ class ContentViewerController < ApplicationController
   def per_page
     12
   end
+
+  def redirect_to_translation
+    locale = FastGettext.locale
+    if !@page.language.nil? && @page.language != locale
+      translations = [@page.native_translation] + @page.native_translation.translations
+      urls = translations.map{ |t| URI.parse(url_for(t.url)).path }
+      urls << URI.parse(url_for(profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }))).path
+      urls << URI.parse(url_for(profile.admin_url.merge(:controller => 'cms', :action => 'new'))).path
+      referer = URI.parse(url_for(request.referer)).path unless request.referer.blank?
+      unless urls.include?(referer)
+        translations.each do |translation|
+          if translation.language == locale
+            @page = translation
+            redirect_to :profile => @page.profile.identifier, :page => @page.explode_path
+          end
+        end
+      end
+    end
+  end
+
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 36aa1f7..4c3c8ee 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -27,7 +27,7 @@ module ApplicationHelper
   include AccountHelper
 
   def locale
-    FastGettext.locale
+    (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
   end
 
   def load_web2_conf
diff --git a/app/helpers/content_viewer_helper.rb b/app/helpers/content_viewer_helper.rb
index a835779..b43b76d 100644
--- a/app/helpers/content_viewer_helper.rb
+++ b/app/helpers/content_viewer_helper.rb
@@ -35,4 +35,16 @@ module ContentViewerHelper
     text && (text.first(40) + (text.size > 40 ? '…' : ''))
   end
 
+  def article_translations(article)
+    unless article.native_translation.translations.empty?
+      links = (article.native_translation.translations + [article.native_translation]).map do |translation|
+        { Noosfero.locales[translation.language] => { :href => url_for(translation.url) } }
+      end
+      content_tag(:div, link_to(_('Translations'), '#',
+                                :onclick => "toggleSubmenu(this, '#{_('Translations')}', #{links.to_json}); return false",
+                                :class => 'article-translations-menu simplemenu-trigger up'),
+                  :class => 'article-translations')
+    end
+  end
+
 end
diff --git a/app/models/article.rb b/app/models/article.rb
index 18dc027..a2869d7 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -28,6 +28,10 @@ class Article < ActiveRecord::Base
 
   belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
 
+  has_many :translations, :class_name => 'Article', :foreign_key => :translation_of_id
+  belongs_to :translation_of, :class_name => 'Article', :foreign_key => :translation_of_id
+  before_destroy :rotate_translations
+
   before_create do |article|
     article.published_at = article.created_at if article.published_at.nil?
   end
@@ -47,6 +51,10 @@ class Article < ActiveRecord::Base
   URL_FORMAT = /\A(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\Z/ix
 
   validates_format_of :external_link, :with => URL_FORMAT, :if => lambda { |article| !article.external_link.blank? }
+  validate :known_language
+  validate :used_translation
+  validate :native_translation_must_have_language
+  validate :translation_must_have_language
 
   def is_trackable?
     self.published? && self.notifiable? && self.advertise?
@@ -245,6 +253,65 @@ class Article < ActiveRecord::Base
     false
   end
 
+  named_scope :native_translations, :conditions => { :translation_of_id => nil }
+
+  def translatable?
+    false
+  end
+
+  def native_translation
+    self.translation_of.nil? ? self : self.translation_of
+  end
+
+  def possible_translations
+    possibilities = Noosfero.locales.keys - self.native_translation.translations(:select => :language).map(&:language) - [self.native_translation.language]
+    possibilities << self.language unless self.language_changed?
+    possibilities
+  end
+
+  def known_language
+    unless self.language.blank?
+      errors.add(:language, N_('Language not supported by Noosfero')) unless Noosfero.locales.key?(self.language)
+    end
+  end
+
+  def used_translation
+    unless self.language.blank? or self.translation_of.nil?
+      errors.add(:language, N_('Language is already used')) unless self.possible_translations.include?(self.language)
+    end
+  end
+
+  def translation_must_have_language
+    unless self.translation_of.nil?
+      errors.add(:language, N_('Language must be choosen')) if self.language.blank?
+    end
+  end
+
+  def native_translation_must_have_language
+    unless self.translation_of.nil?
+      errors.add_to_base(N_('A language must be choosen for the native article')) if self.translation_of.language.blank?
+    end
+  end
+
+  def rotate_translations
+    unless self.translations.empty?
+      rotate = self.translations
+      root = rotate.shift
+      root.update_attribute(:translation_of_id, nil)
+      root.translations = rotate
+    end
+  end
+
+  def get_translation_to(locale)
+    if self.language.nil? || self.language == locale
+      self
+    elsif self.native_translation.language == locale
+      self.native_translation
+    else
+      self.native_translation.translations.first(:conditions => { :language => locale }) || self
+    end
+  end
+
   def published?
     if self.published
       if self.parent && !self.parent.published?
diff --git a/app/models/blog.rb b/app/models/blog.rb
index 55d838d..3fd7950 100644
--- a/app/models/blog.rb
+++ b/app/models/blog.rb
@@ -57,4 +57,8 @@ class Blog < Folder
   settings_items :visualization_format, :type => :string, :default => 'full'
   validates_inclusion_of :visualization_format, :in => [ 'full', 'short' ], :if => :visualization_format
 
+  settings_items :display_posts_in_current_language, :type => :boolean, :default => true
+
+  alias :display_posts_in_current_language? :display_posts_in_current_language
+
 end
diff --git a/app/models/blog_archives_block.rb b/app/models/blog_archives_block.rb
index e6b8284..6d370b9 100644
--- a/app/models/blog_archives_block.rb
+++ b/app/models/blog_archives_block.rb
@@ -24,7 +24,7 @@ class BlogArchivesBlock < Block
     owner_blog = self.blog
     return nil unless owner_blog
     results = ''
-    owner_blog.posts.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|
+    owner_blog.posts.native_translations.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|
       results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})"))
       results << "<ul class='#{year}-archive'>"
       results_by_year.group_by{|i| [ ('%02d' % i.published_at.month()), gettext(MONTHS[i.published_at.month() - 1])]}.sort.reverse.each do |month, results_by_month|
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 6cee1d2..34cc7fe 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -108,7 +108,7 @@ class Environment < ActiveRecord::Base
       'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
       'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'),
       'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'),
-      'xmpp_chat' => _('XMPP/Jabber based chat'),
+      'xmpp_chat' => _('XMPP/Jabber based chat')
     }
   end
 
diff --git a/app/models/event.rb b/app/models/event.rb
index 6f103c0..5746dbb 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -120,6 +120,10 @@ class Event < Article
     true
   end
 
+  def translatable?
+    true
+  end
+
   include MaybeAddHttp
 
 end
diff --git a/app/models/rss_feed.rb b/app/models/rss_feed.rb
index d848428..6cad49d 100644
--- a/app/models/rss_feed.rb
+++ b/app/models/rss_feed.rb
@@ -62,7 +62,8 @@ class RssFeed < Article
   include ActionController::UrlWriter
   def fetch_articles
     if parent && parent.has_posts?
-      return parent.posts.find(:all, :conditions => ['published = ?', true], :limit => self.limit, :order => 'id desc')
+      language = self.language.blank? ? {} : { :language => self.language }
+      return parent.posts.find(:all, :conditions => { :published => true }.merge(language), :limit => self.limit, :order => 'id desc')
     end
 
     articles =
diff --git a/app/models/textile_article.rb b/app/models/textile_article.rb
index 8bf2c4d..ea25554 100644
--- a/app/models/textile_article.rb
+++ b/app/models/textile_article.rb
@@ -16,4 +16,8 @@ class TextileArticle < TextArticle
     true
   end
 
+  def translatable?
+    true
+  end
+
 end
diff --git a/app/models/tiny_mce_article.rb b/app/models/tiny_mce_article.rb
index 3788945..160587f 100644
--- a/app/models/tiny_mce_article.rb
+++ b/app/models/tiny_mce_article.rb
@@ -19,4 +19,8 @@ class TinyMceArticle < TextArticle
     true
   end
 
+  def translatable?
+    true
+  end
+
 end
diff --git a/app/views/cms/_blog.rhtml b/app/views/cms/_blog.rhtml
index 7db52f2..b3304d2 100644
--- a/app/views/cms/_blog.rhtml
+++ b/app/views/cms/_blog.rhtml
@@ -56,8 +56,11 @@
 
 <%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %>
 
+<%= labelled_check_box(_('Display posts in current language, if available'), 'article[display_posts_in_current_language]', '1', @article.display_posts_in_current_language?) %>
+
 <% f.fields_for 'feed', @article.feed do |feed| %>
   <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %>
+  <%= labelled_form_field(_('Include in RSS Feed only posts from language:'), feed.select(:language, { _('All') => nil }.merge(Noosfero.locales.invert))) %>
 <% end %>
 
 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %>
diff --git a/app/views/cms/_event.rhtml b/app/views/cms/_event.rhtml
index 881ade3..f5fd5a6 100644
--- a/app/views/cms/_event.rhtml
+++ b/app/views/cms/_event.rhtml
@@ -5,6 +5,8 @@
 
 <%= required f.text_field('name', :size => '64') %>
 
+<%= render :partial => 'translatable' %>
+
 <%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %>
 
 <%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %>
diff --git a/app/views/cms/_textile_article.rhtml b/app/views/cms/_textile_article.rhtml
index e7490c6..81fa905 100644
--- a/app/views/cms/_textile_article.rhtml
+++ b/app/views/cms/_textile_article.rhtml
@@ -4,6 +4,8 @@
 
 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
 
+<%= render :partial => 'translatable' %>
+
 <br style="clear: both;"/>
 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
 <em><%= _('Used when a short version of your text is needed.') %></em>
diff --git a/app/views/cms/_tiny_mce_article.rhtml b/app/views/cms/_tiny_mce_article.rhtml
index b278ba6..c05439f 100644
--- a/app/views/cms/_tiny_mce_article.rhtml
+++ b/app/views/cms/_tiny_mce_article.rhtml
@@ -11,6 +11,8 @@
     <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
   <% end %>
 
+  <%= render :partial => 'translatable' %>
+
   <br style="clear: both;"/>
   <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
   <em><%= _('Used when a short version of your text is needed.') %></em>
diff --git a/app/views/cms/_translatable.rhtml b/app/views/cms/_translatable.rhtml
new file mode 100644
index 0000000..4f69bed
--- /dev/null
+++ b/app/views/cms/_translatable.rhtml
@@ -0,0 +1,7 @@
+<% if @article.translatable? %>
+  <div class="article-translation-field">
+    <%= label :article, :language, _('Language') %> <br />
+    <%= select :article, :language, @locales, { :selected => @selected_locale, :include_blank => true } %>
+    <%= hidden_field(:article, :translation_of_id) %>
+  </div>
+<% end %>
diff --git a/app/views/content_viewer/view_page.rhtml b/app/views/content_viewer/view_page.rhtml
index 7b040b7..4129b11 100644
--- a/app/views/content_viewer/view_page.rhtml
+++ b/app/views/content_viewer/view_page.rhtml
@@ -35,6 +35,11 @@
       <% end %>
       <% if !(profile.kind_of?(Enterprise) && environment.enabled?('disable_cms')) %>
         <% if !@page.gallery? %>
+          <%= link_to _('Add translation'),
+              profile.admin_url.merge(:controller => 'cms', :action => 'new',
+                                      :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)),
+                                      :type => @page.type, :article => { :translation_of_id => @page.native_translation.id }),
+              :class => 'button with-text icon-locale' if @page.translatable? && !@page.native_translation.language.blank? %>
           <%= lightbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %>
         <% end %>
         <% if (@page.folder? && !@page.has_posts?) || (@page.parent && @page.parent.folder? && !@page.parent.has_posts?) %>
@@ -46,6 +51,7 @@
       <% end %>
     </div>
   <% end %>
+  <%= article_translations(@page) %>
   <div id="article-header">
     <%= link_to(image_tag('icons-mime/rss-feed.png'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %>
     <%= article_title(@page, :no_link => true) %>
diff --git a/db/migrate/20101205034144_add_language_and_translation_of_id_to_article.rb b/db/migrate/20101205034144_add_language_and_translation_of_id_to_article.rb
new file mode 100644
index 0000000..3db0d88
--- /dev/null
+++ b/db/migrate/20101205034144_add_language_and_translation_of_id_to_article.rb
@@ -0,0 +1,29 @@
+class AddLanguageAndTranslationOfIdToArticle < ActiveRecord::Migration
+  def self.up
+    add_column :articles, :translation_of_id, :interger
+    add_column :articles, :language, :string
+
+    add_column :article_versions, :translation_of_id, :interger
+    add_column :article_versions, :language, :string
+
+    add_index  :articles, :translation_of_id
+
+    select_all("select id, setting from articles where type = 'Blog'").each do |blog|
+      settings = YAML.load(blog['setting'])
+      settings[:display_posts_in_current_language] = true
+      assignments = ActiveRecord::Base.sanitize_sql_for_assignment(:setting => settings.to_yaml)
+      update("update articles set %s where id = %d" % [assignments, blog['id']])
+    end
+
+  end
+
+  def self.down
+    remove_index :articles, :translation_of_id
+
+    remove_column :article_versions, :translation_of_id
+    remove_column :article_versions, :language
+
+    remove_column :articles, :language
+    remove_column :articles, :translation_of_id
+  end
+end
diff --git a/features/edit_article.feature b/features/edit_article.feature
index a1bd43c..5c2463a 100644
--- a/features/edit_article.feature
+++ b/features/edit_article.feature
@@ -133,3 +133,32 @@ Feature: edit article
     Then I should be on "My new article" edit page
     And the "Title" field should contain "My new article"
     And the "Text" field should contain "text for the new article"
+
+  Scenario: add a translation to an article
+    Given I am on Joao Silva's sitemap
+    And I follow "Save the whales"
+    Then I should not see "Add translation"
+    And I follow "Edit"
+    And I select "English" from "Language"
+    Then I press "Save"
+    And I follow "Add translation"
+    And I fill in "Title" with "Mi neuvo artículo"
+    And I select "Español" from "Language"
+    When I press "Save"
+    Then I should be on /joaosilva/save-the-whales
+    And I should see "Translations"
+
+  Scenario: not add a translation without a language
+    Given the following articles
+      | owner     | name               | language |
+      | joaosilva | Article in English | en       |
+    And I am on Joao Silva's sitemap
+    And I follow "Article in English"
+    And I follow "Add translation"
+    And I fill in "Title" with "Article in Portuguese"
+    When I press "Save"
+    Then I should see "Language must be choosen"
+    And I select "Português" from "Language"
+    When I press "Save"
+    Then I should not see "Language must be choosen"
+    And I should be on /joaosilva/article-in-english
diff --git a/public/designs/icons/tango/style.css b/public/designs/icons/tango/style.css
index 5b7379f..bb88794 100644
--- a/public/designs/icons/tango/style.css
+++ b/public/designs/icons/tango/style.css
@@ -75,3 +75,4 @@
 .icon-reply            { background-image: url(Tango/16x16/actions/mail-reply-sender.png) }
 .icon-newforum   { background-image: url(Tango/16x16/apps/system-users.png) }
 .icon-newgallery   { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) }
+.icon-locale           { background-image: url(Tango/16x16/apps/preferences-desktop-locale.png) }
\ No newline at end of file
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 1c97a3d..1aea4ea 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -5231,6 +5231,49 @@ h1#agenda-title {
 	margin-top: 10px;
 }
 
+
+.article-translations-menu {
+    float: right;
+    bottom: -15px;
+}
+
+.opera .article-translations-menu {
+  bottom: -8px;
+}
+
+.webkit .article-translations-menu {
+  bottom: -10px;
+}
+
+.article-translations-menu {
+    text-decoration: none !important;
+    height: 0;
+}
+
+.article-translations .menu-submenu-header,
+.article-translations .menu-submenu-content,
+.article-translations .menu-submenu-footer {
+    background: none;
+}
+
+.article-translations .menu-submenu {
+    bottom: auto;
+    top: 60px;
+    right: 0;
+    border: 1px solid;
+    background: #fff;
+}
+
+.msie7 .article-translations .menu-submenu,
+.webkit .article-translations .menu-submenu,
+.opera .article-translations .menu-submenu {
+  top: 30px;
+}
+
+.article-translations .menu-submenu-list {
+    list-style: none;
+}
+
 /* Forum */
 
 .forum-posts .pagination {
diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb
index 9fb3771..81af15d 100644
--- a/test/functional/application_controller_test.rb
+++ b/test/functional/application_controller_test.rb
@@ -419,4 +419,26 @@ class ApplicationControllerTest < Test::Unit::TestCase
     assert_tag :tag => 'meta', :attributes => { :name => 'description', :content => assigns(:environment).name }
   end
 
+  should 'set html lang as the article language if an article is present and has a language' do
+    a = fast_create(Article, :name => 'test article', :language => 'fr')
+    @controller.instance_variable_set('@page', a)
+    FastGettext.stubs(:locale).returns('es')
+    get :index
+    assert_tag :html, :attributes => { :lang => 'fr' }
+  end
+
+  should 'set html lang as locale if no page present' do
+    FastGettext.stubs(:locale).returns('es')
+    get :index
+    assert_tag :html, :attributes => { :lang => 'es' }
+  end
+
+  should 'set html lang as locale if page has no language' do
+    a = fast_create(Article, :name => 'test article', :language => nil)
+    @controller.instance_variable_set('@page', a)
+    FastGettext.stubs(:locale).returns('es')
+    get :index
+    assert_tag :html, :attributes => { :lang => 'es' }
+  end
+
 end
diff --git a/test/functional/cms_controller_test.rb b/test/functional/cms_controller_test.rb
index 7423824..86898d5 100644
--- a/test/functional/cms_controller_test.rb
+++ b/test/functional/cms_controller_test.rb
@@ -1410,4 +1410,63 @@ class CmsControllerTest < Test::Unit::TestCase
     assert_no_tag :tag => 'a', :attributes => { :href => "/myprofile/#{profile.identifier}/cms/upload_files?parent_id=#{profile.forum.id}"}
   end
 
+  should 'article language should be selected' do
+    textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'ru')
+    get :edit, :profile => @profile.identifier, :id => textile.id
+    assert_tag :option, :attributes => { :selected => 'selected', :value => 'ru' }, :parent => {
+      :tag => 'select', :attributes => { :id => 'article_language'} }
+  end
+
+  should 'list possible languages and include blank option' do
+    get :new, :profile => @profile.identifier, :type => 'TextileArticle'
+    assert_equal Noosfero.locales.invert, assigns(:locales)
+    assert_tag :option, :attributes => { :value => '' }, :parent => {
+      :tag => 'select', :attributes => { :id => 'article_language'} }
+  end
+
+  should 'add translation to an article' do
+    textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'ru')
+    assert_difference Article, :count do
+      post :new, :profile => @profile.identifier, :type => 'TextileArticle', :article => { :name => 'english translation', :translation_of_id => textile.id, :language => 'en' }
+    end
+  end
+
+  should 'not display language selection if article is not translatable' do
+    blog = fast_create(Blog, :name => 'blog', :profile_id => @profile.id)
+    get :edit, :profile => @profile.identifier, :id => blog.id
+    assert_no_tag :select, :attributes => { :id => 'article_language'}
+  end
+
+  should 'display display posts in current language input checked on edit blog' do
+    get :new, :profile => profile.identifier, :type => 'Blog'
+    assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
+  end
+
+  should 'update to false blog display posts in current language setting' do
+    profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => true)
+    post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :display_posts_in_current_language => false }
+    profile.blog.reload
+    assert !profile.blog.display_posts_in_current_language?
+  end
+
+  should 'update to true blog display posts in current language setting' do
+    profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => false)
+    post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :display_posts_in_current_language => true }
+    profile.blog.reload
+    assert profile.blog.display_posts_in_current_language?
+  end
+
+  should 'be checked display posts in current language checkbox' do
+    profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => true)
+    get :edit, :profile => profile.identifier, :id => profile.blog.id
+    assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
+  end
+
+  should 'be unchecked display posts in current language checkbox' do
+    profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => false)
+    get :edit, :profile => profile.identifier, :id => profile.blog.id
+    assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]' }
+    assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
+  end
+
 end
diff --git a/test/functional/content_viewer_controller_test.rb b/test/functional/content_viewer_controller_test.rb
index 36c0ef1..52e68a5 100644
--- a/test/functional/content_viewer_controller_test.rb
+++ b/test/functional/content_viewer_controller_test.rb
@@ -1095,4 +1095,147 @@ class ContentViewerControllerTest < Test::Unit::TestCase
     assert_tag :tag => 'div', :attributes => { :class => /main-block/ }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{forum.id}" }, :content => 'Configure forum' }
   end
 
+  should 'display add translation link if article is translatable' do
+    login_as @profile.identifier
+    textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'en')
+    get :view_page, :profile => @profile.identifier, :page => textile.explode_path
+    assert_tag :a, :attributes => { :href => "/myprofile/#{profile.identifier}/cms/new?article%5Btranslation_of_id%5D=#{textile.id}&amp;type=#{TextileArticle}" }
+  end
+
+  should 'not display add translation link if article is not translatable' do
+    login_as @profile.identifier
+    blog = fast_create(Blog, :profile_id => @profile.id, :path => 'blog')
+    get :view_page, :profile => @profile.identifier, :page => blog.explode_path
+    assert_no_tag :a, :attributes => { :content => 'Add translation', :class => /icon-locale/ }
+  end
+
+  should 'not display add translation link if article hasnt a language defined' do
+    login_as @profile.identifier
+    textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile')
+    get :view_page, :profile => @profile.identifier, :page => textile.explode_path
+    assert_no_tag :a, :attributes => { :content => 'Add translation', :class => /icon-locale/ }
+  end
+
+  should 'diplay translations link if article has translations' do
+    login_as @profile.identifier
+    textile     = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'en')
+    translation = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'translation', :language => 'es', :translation_of_id => textile)
+    get :view_page, :profile => @profile.identifier, :page => textile.explode_path
+    assert_tag :a, :attributes => { :class => /article-translations-menu/, :onclick => /toggleSubmenu/ }
+  end
+
+  should 'be redirected to translation if article is a root' do
+    @request.env['HTTP_REFERER'] = 'http://some.path'
+    FastGettext.stubs(:locale).returns('es')
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
+    assert_redirected_to :profile => @profile.identifier, :page => es_article.explode_path
+    assert_equal es_article, assigns(:page)
+  end
+
+  should 'be redirected to translation' do
+    @request.env['HTTP_REFERER'] = 'http://some.path'
+    FastGettext.stubs(:locale).returns('en')
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
+    assert_redirected_to :profile => @profile.identifier, :page => en_article.explode_path
+    assert_equal en_article, assigns(:page)
+  end
+
+  should 'not be redirected if already in translation' do
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    @request.env['HTTP_REFERER'] = "http://localhost:3000/#{@profile.identifier}/#{es_article.path}"
+    FastGettext.stubs(:locale).returns('es')
+    get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
+    assert_response :success
+    assert_equal es_article, assigns(:page)
+  end
+
+  should 'not be redirected if article does not have a language' do
+    FastGettext.stubs(:locale).returns('es')
+    article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'article')
+    get :view_page, :profile => @profile.identifier, :page => article.explode_path
+    assert_response :success
+    assert_equal article, assigns(:page)
+  end
+
+  should 'not be redirected if http_referer is a translation' do
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    @request.env['HTTP_REFERER'] = "http://localhost:3000/#{@profile.identifier}/#{es_article.path}"
+    FastGettext.stubs(:locale).returns('es')
+    get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
+    assert_response :success
+    assert_equal en_article, assigns(:page)
+  end
+
+  should 'be redirected if http_referer is nil' do
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    @request.env['HTTP_REFERER'] = nil
+    FastGettext.stubs(:locale).returns('es')
+    get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
+    assert_redirected_to :profile => @profile.identifier, :page => es_article.explode_path
+    assert_equal es_article, assigns(:page)
+  end
+
+  should 'not be redirected to transition if came from edit' do
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    FastGettext.stubs(:locale).returns('es')
+    @request.env['HTTP_REFERER'] = "http://localhost/myprofile/#{@profile.identifier}/cms/edit/#{en_article.id}"
+    get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
+    assert_response :success
+    assert_equal es_article, assigns(:page)
+  end
+
+  should 'not be redirected to transition if came from new' do
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    FastGettext.stubs(:locale).returns('es')
+    @request.env['HTTP_REFERER'] = "http://localhost/myprofile/#{@profile.identifier}/cms/new"
+    get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
+    assert_response :success
+    assert_equal es_article, assigns(:page)
+  end
+
+  should 'replace article for his translation at blog listing if blog option is enabled' do
+    FastGettext.stubs(:locale).returns('es')
+    blog = fast_create(Blog, :profile_id => profile.id, :path => 'blog')
+    blog.stubs(:display_posts_in_current_language).returns(true)
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    blog.posts = [en_article, es_article]
+
+    get :view_page, :profile => @profile.identifier, :page => blog.explode_path
+    assert_tag :div, :attributes => { :id => "post-#{es_article.id}" }
+    assert_no_tag :div, :attributes => { :id => "post-#{en_article.id}" }
+  end
+
+  should 'list all posts at blog listing if blog option is disabled' do
+    FastGettext.stubs(:locale).returns('es')
+    blog = Blog.create!(:name => 'A blog test', :profile => profile, :display_posts_in_current_language => false)
+    blog.posts << es_post = TextileArticle.create!(:name => 'Spanish Post', :profile => profile, :parent => blog, :language => 'es')
+    blog.posts << en_post = TextileArticle.create!(:name => 'English Post', :profile => profile, :parent => blog, :language => 'en', :translation_of_id => es_post.id)
+    get :view_page, :profile => profile.identifier, :page => [blog.path]
+    assert_equal 2, assigns(:posts).size
+    assert_tag :div, :attributes => { :id => "post-#{es_post.id}" }
+    assert_tag :div, :attributes => { :id => "post-#{en_post.id}" }
+  end
+
+  should 'display only native translations at blog listing if blog option is enabled' do
+    FastGettext.stubs(:locale).returns('es')
+    blog = fast_create(Blog, :profile_id => profile.id, :path => 'blog')
+    blog.stubs(:display_posts_in_current_language).returns(true)
+    en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
+    es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
+    blog.posts = [en_article, es_article]
+
+    get :view_page, :profile => @profile.identifier, :page => blog.explode_path
+    assert_equal [es_article], assigns(:posts)
+  end
+
 end
diff --git a/test/unit/article_test.rb b/test/unit/article_test.rb
index 187d999..385d937 100644
--- a/test/unit/article_test.rb
+++ b/test/unit/article_test.rb
@@ -1226,4 +1226,174 @@ class ArticleTest < Test::Unit::TestCase
     assert_equal [g], p.articles.galleries
   end
 
+  should 'has many translations' do
+    a = build(Article)
+    assert_raises(ActiveRecord::AssociationTypeMismatch) { a.translations << 1 }
+    assert_nothing_raised { a.translations << build(Article) }
+  end
+
+  should 'belongs to translation of' do
+    a = build(Article)
+    assert_raises(ActiveRecord::AssociationTypeMismatch) { a.translation_of = 1 }
+    assert_nothing_raised { a.translation_of = build(Article) }
+  end
+
+  should 'has language' do
+    a = build(Article)
+    assert_nothing_raised { a.language = 'en' }
+  end
+
+  should 'validade inclusion of language' do
+    a = build(Article)
+    a.language = '12'
+    a.valid?
+    assert a.errors.invalid?(:language)
+    a.language = 'en'
+    a.valid?
+    assert !a.errors.invalid?(:language)
+  end
+
+  should 'language can be blank' do
+    a = build(Article)
+    a.valid?
+    assert !a.errors.invalid?(:language)
+    a.language = ''
+    a.valid?
+    assert !a.errors.invalid?(:language)
+  end
+
+  should 'article is not translatable' do
+    a = build(Article)
+    assert !a.translatable?
+  end
+
+  should 'get native translation' do
+    native_article = fast_create(Article)
+    article_translation = fast_create(Article)
+    native_article.translations << article_translation
+    assert_equal native_article, native_article.native_translation
+    assert_equal native_article, article_translation.native_translation
+  end
+
+  should 'list possible translations' do
+    native_article = fast_create(Article, :language => 'pt')
+    article_translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id)
+    possible_translations = native_article.possible_translations
+    assert !possible_translations.include?('en')
+    assert possible_translations.include?('pt')
+  end
+
+  should 'verify if translation is already in use' do
+    native_article = fast_create(Article, :language => 'pt')
+    article_translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id)
+    a = build(Article)
+    a.language = 'en'
+    a.translation_of = native_article
+    a.valid?
+    assert a.errors.invalid?(:language)
+    a.language = 'es'
+    a.valid?
+    assert !a.errors.invalid?(:language)
+  end
+
+  should 'verify if native translation is already in use' do
+    native_article = fast_create(Article, :language => 'pt')
+    a = build(Article)
+    a.language = 'pt'
+    a.translation_of = native_article
+    a.valid?
+    assert a.errors.invalid?(:language)
+    a.language = 'es'
+    a.valid?
+    assert !a.errors.invalid?(:language)
+  end
+
+  should 'translation have a language' do
+    native_article = fast_create(Article, :language => 'pt')
+    a = build(Article)
+    a.translation_of = native_article
+    a.valid?
+    assert a.errors.invalid?(:language)
+    a.language = 'en'
+    a.valid?
+    assert !a.errors.invalid?(:language)
+  end
+
+  should 'native translation have a language' do
+    native_article = fast_create(Article)
+    a = build(Article)
+    a.language = 'en'
+    a.translation_of = native_article
+    a.valid?
+    n = a.errors.count
+    native_article.language = 'pt'
+    native_article.save
+    a.valid?
+    assert_equal n - 1, a.errors.count
+  end
+
+  should 'rotate translations when root article is destroyed' do
+    native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    translation1 = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
+    translation2 = fast_create(Article, :language => 'es', :translation_of_id => native_article.id, :profile_id => @profile.id)
+    native_article.destroy
+    assert translation1.translation_of.nil?
+    assert translation1.translations.include?(translation2)
+  end
+
+  should 'rotate one translation when root article is destroyed' do
+    native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
+    native_article.destroy
+    assert translation.translation_of.nil?
+    assert translation.translations.empty?
+  end
+
+  should 'get self if article does not a language' do
+    article = fast_create(Article, :profile_id => @profile.id)
+    assert_equal article, article.get_translation_to('en')
+  end
+
+  should 'get self if article is the translation' do
+    article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    assert_equal article, article.get_translation_to('pt')
+  end
+
+  should 'get the native translation if it is the translation' do
+    native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
+    assert_equal native_article, translation.get_translation_to('pt')
+  end
+
+  should 'get the translation if article has translation' do
+    native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
+    assert_equal translation, native_article.get_translation_to('en')
+  end
+
+  should 'get self if article does not has a translation' do
+    native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
+    assert_equal native_article, native_article.get_translation_to('en')
+  end
+
+  should 'get only non translated articles' do
+    p = fast_create(Profile)
+    native = fast_create(Article, :language => 'pt', :profile_id => p.id)
+    translation = fast_create(Article, :language => 'en', :translation_of_id => native.id, :profile_id => p.id)
+    assert_equal [native], p.articles.native_translations
+  end
+
+  should 'not list own language as a possible translation if language has changed' do
+    a = build(Article, :language => 'pt')
+    assert !a.possible_translations.include?('pt')
+    a = fast_create(Article, :language => 'pt')
+    a.language = 'en'
+    assert !a.possible_translations.include?('en')
+  end
+
+  should 'list own language as a possible translation if language has not changed' do
+    a = fast_create(Article, :language => 'pt')
+    assert a.possible_translations.include?('pt')
+  end
+
 end
diff --git a/test/unit/blog_archives_block_test.rb b/test/unit/blog_archives_block_test.rb
index 914bb8a..a15656c 100644
--- a/test/unit/blog_archives_block_test.rb
+++ b/test/unit/blog_archives_block_test.rb
@@ -102,4 +102,36 @@ class BlogArchivesBlockTest < ActiveSupport::TestCase
     assert_no_match(/blog-two/m, block.content)
   end
 
+  should 'list amount native posts by year' do
+    date = DateTime.parse('2008-01-01')
+    blog = profile.blog
+    2.times do |i|
+      post = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
+                         :parent_id => blog.id, :language => 'en')
+      post.update_attribute(:published_at, date)
+      translation = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
+                  :parent_id => blog.id, :language => 'en', :translation_of_id => post.id)
+      translation.update_attribute(:published_at, date)
+    end
+    block = BlogArchivesBlock.new
+    block.stubs(:owner).returns(profile)
+    assert_tag_in_string block.content, :tag => 'li', :content => '2008 (2)'
+  end
+
+  should 'list amount native posts by month' do
+    date = DateTime.parse('2008-01-01')
+    blog = profile.blog
+    2.times do |i|
+      post = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
+                         :parent_id => blog.id, :language => 'en')
+      post.update_attribute(:published_at, date)
+      translation = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
+                  :parent_id => blog.id, :language => 'en', :translation_of_id => post.id)
+      translation.update_attribute(:published_at, date)
+    end
+    block = BlogArchivesBlock.new
+    block.stubs(:owner).returns(profile)
+    assert_tag_in_string block.content, :tag => 'a', :content => 'January (2)', :attributes => {:href => /^http:\/\/.*\/flatline\/blog-one\?month=01&year=2008$/ }
+  end
+
 end
diff --git a/test/unit/blog_test.rb b/test/unit/blog_test.rb
index 49bfd6b..13e1d63 100644
--- a/test/unit/blog_test.rb
+++ b/test/unit/blog_test.rb
@@ -175,4 +175,20 @@ class BlogTest < ActiveSupport::TestCase
     assert Blog.new.has_posts?
   end
 
+  should 'display posts in current language by default' do
+    blog = Blog.new
+    assert blog.display_posts_in_current_language
+    assert blog.display_posts_in_current_language?
+  end
+
+  should 'update display posts in current language setting' do
+    p = create_user('testuser').person
+    p.articles << Blog.new(:profile => p, :name => 'Blog test')
+    blog = p.blog
+    blog.display_posts_in_current_language = false
+    assert blog.save! && blog.reload
+    assert !blog.reload.display_posts_in_current_language
+    assert !blog.reload.display_posts_in_current_language?
+  end
+
 end
diff --git a/test/unit/event_test.rb b/test/unit/event_test.rb
index c5f519c..678e842 100644
--- a/test/unit/event_test.rb
+++ b/test/unit/event_test.rb
@@ -265,4 +265,10 @@ class EventTest < ActiveSupport::TestCase
     assert_match  /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.description
     assert_match  /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.address
   end
+
+  should 'be translatable' do
+    e = Event.new
+    assert e.translatable?
+  end
+
 end
diff --git a/test/unit/rss_feed_test.rb b/test/unit/rss_feed_test.rb
index fbe1d5a..41d8f81 100644
--- a/test/unit/rss_feed_test.rb
+++ b/test/unit/rss_feed_test.rb
@@ -238,4 +238,24 @@ class RssFeedTest < Test::Unit::TestCase
     assert_not_nil RssFeed.new.to_html
   end
 
+  should 'include posts from all languages' do
+    profile = create_user('testuser').person
+    blog = Blog.create!(:name => 'blog-test', :profile => profile, :language => nil)
+    blog.posts << en_post = fast_create(TextArticle, :name => "English", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'en')
+    blog.posts << es_post = fast_create(TextArticle, :name => "Spanish", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'es')
+
+    assert blog.feed.fetch_articles.include?(en_post)
+    assert blog.feed.fetch_articles.include?(es_post)
+  end
+
+  should 'include only posts from some language' do
+    profile = create_user('testuser').person
+    blog = Blog.create!(:name => 'blog-test', :profile => profile)
+    blog.feed.update_attributes! :language => 'es'
+    blog.posts << en_post = fast_create(TextArticle, :name => "English", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'en')
+    blog.posts << es_post = fast_create(TextArticle, :name => "Spanish", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'es')
+
+    assert_equal [es_post], blog.feed.fetch_articles
+  end
+
 end
diff --git a/test/unit/textile_article_test.rb b/test/unit/textile_article_test.rb
index 1c9629f..2060389 100644
--- a/test/unit/textile_article_test.rb
+++ b/test/unit/textile_article_test.rb
@@ -146,4 +146,9 @@ class TextileArticleTest < Test::Unit::TestCase
     assert_equal false, a.is_trackable?
   end
 
+  should 'be translatable' do
+    a = TextileArticle.new
+    assert a.translatable?
+  end
+
 end
diff --git a/test/unit/tiny_mce_article_test.rb b/test/unit/tiny_mce_article_test.rb
index c4f3a84..ea639b8 100644
--- a/test/unit/tiny_mce_article_test.rb
+++ b/test/unit/tiny_mce_article_test.rb
@@ -237,5 +237,8 @@ class TinyMceArticleTest < Test::Unit::TestCase
     assert_equal false, a.is_trackable?
   end
 
-
+  should 'be translatable' do
+    a = TinyMceArticle.new
+    assert a.translatable?
+  end
 end
-- 
1.5.4.3

