Commit d7fee6b905378a4d92b4c9da99b180003920b470

Authored by Caio Almeida
Committed by Antonio Terceiro
1 parent f5703d91

Adding articles translation support

(ActionItem894)
app/controllers/my_profile/cms_controller.rb
@@ -89,6 +89,7 @@ class CmsController < MyProfileController @@ -89,6 +89,7 @@ class CmsController < MyProfileController
89 @article = profile.articles.find(params[:id]) 89 @article = profile.articles.find(params[:id])
90 @parent_id = params[:parent_id] 90 @parent_id = params[:parent_id]
91 @type = params[:type] || @article.class.to_s 91 @type = params[:type] || @article.class.to_s
  92 + translations if @article.translatable?
92 continue = params[:continue] 93 continue = params[:continue]
93 94
94 refuse_blocks 95 refuse_blocks
@@ -138,6 +139,8 @@ class CmsController < MyProfileController @@ -138,6 +139,8 @@ class CmsController < MyProfileController
138 @parent_id = parent.id 139 @parent_id = parent.id
139 end 140 end
140 141
  142 + translations if @article.translatable?
  143 +
141 @article.profile = profile 144 @article.profile = profile
142 @article.last_changed_by = user 145 @article.last_changed_by = user
143 146
@@ -367,5 +370,11 @@ class CmsController < MyProfileController @@ -367,5 +370,11 @@ class CmsController < MyProfileController
367 def per_page 370 def per_page
368 10 371 10
369 end 372 end
  373 +
  374 + def translations
  375 + @locales = Noosfero.locales.invert.reject { |name, lang| !@article.possible_translations.include?(lang) }
  376 + @selected_locale = @article.language || FastGettext.locale
  377 + end
  378 +
370 end 379 end
371 380
app/controllers/public/content_viewer_controller.rb
@@ -51,6 +51,8 @@ class ContentViewerController < ApplicationController @@ -51,6 +51,8 @@ class ContentViewerController < ApplicationController
51 return 51 return
52 end 52 end
53 53
  54 + redirect_to_translation
  55 +
54 # At this point the page will be showed 56 # At this point the page will be showed
55 @page.hit 57 @page.hit
56 58
@@ -85,7 +87,11 @@ class ContentViewerController < ApplicationController @@ -85,7 +87,11 @@ class ContentViewerController < ApplicationController
85 @page.posts 87 @page.posts
86 end 88 end
87 89
  90 + posts = posts.native_translations if @page.blog? && @page.display_posts_in_current_language?
  91 +
88 @posts = posts.paginate({ :page => params[:npage], :per_page => @page.posts_per_page }.merge(Article.display_filter(user, profile))) 92 @posts = posts.paginate({ :page => params[:npage], :per_page => @page.posts_per_page }.merge(Article.display_filter(user, profile)))
  93 +
  94 + @posts.map!{ |p| p.get_translation_to(FastGettext.locale) } if @page.blog? && @page.display_posts_in_current_language?
89 end 95 end
90 96
91 if @page.folder? && @page.gallery? 97 if @page.folder? && @page.gallery?
@@ -125,4 +131,24 @@ class ContentViewerController < ApplicationController @@ -125,4 +131,24 @@ class ContentViewerController < ApplicationController
125 def per_page 131 def per_page
126 12 132 12
127 end 133 end
  134 +
  135 + def redirect_to_translation
  136 + locale = FastGettext.locale
  137 + if !@page.language.nil? && @page.language != locale
  138 + translations = [@page.native_translation] + @page.native_translation.translations
  139 + urls = translations.map{ |t| URI.parse(url_for(t.url)).path }
  140 + urls << URI.parse(url_for(profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }))).path
  141 + urls << URI.parse(url_for(profile.admin_url.merge(:controller => 'cms', :action => 'new'))).path
  142 + referer = URI.parse(url_for(request.referer)).path unless request.referer.blank?
  143 + unless urls.include?(referer)
  144 + translations.each do |translation|
  145 + if translation.language == locale
  146 + @page = translation
  147 + redirect_to :profile => @page.profile.identifier, :page => @page.explode_path
  148 + end
  149 + end
  150 + end
  151 + end
  152 + end
  153 +
128 end 154 end
app/helpers/application_helper.rb
@@ -27,7 +27,7 @@ module ApplicationHelper @@ -27,7 +27,7 @@ module ApplicationHelper
27 include AccountHelper 27 include AccountHelper
28 28
29 def locale 29 def locale
30 - FastGettext.locale 30 + (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
31 end 31 end
32 32
33 def load_web2_conf 33 def load_web2_conf
app/helpers/content_viewer_helper.rb
@@ -35,4 +35,16 @@ module ContentViewerHelper @@ -35,4 +35,16 @@ module ContentViewerHelper
35 text && (text.first(40) + (text.size > 40 ? '…' : '')) 35 text && (text.first(40) + (text.size > 40 ? '…' : ''))
36 end 36 end
37 37
  38 + def article_translations(article)
  39 + unless article.native_translation.translations.empty?
  40 + links = (article.native_translation.translations + [article.native_translation]).map do |translation|
  41 + { Noosfero.locales[translation.language] => { :href => url_for(translation.url) } }
  42 + end
  43 + content_tag(:div, link_to(_('Translations'), '#',
  44 + :onclick => "toggleSubmenu(this, '#{_('Translations')}', #{links.to_json}); return false",
  45 + :class => 'article-translations-menu simplemenu-trigger up'),
  46 + :class => 'article-translations')
  47 + end
  48 + end
  49 +
38 end 50 end
app/models/article.rb
@@ -28,6 +28,10 @@ class Article &lt; ActiveRecord::Base @@ -28,6 +28,10 @@ class Article &lt; ActiveRecord::Base
28 28
29 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id' 29 belongs_to :reference_article, :class_name => "Article", :foreign_key => 'reference_article_id'
30 30
  31 + has_many :translations, :class_name => 'Article', :foreign_key => :translation_of_id
  32 + belongs_to :translation_of, :class_name => 'Article', :foreign_key => :translation_of_id
  33 + before_destroy :rotate_translations
  34 +
31 before_create do |article| 35 before_create do |article|
32 article.published_at = article.created_at if article.published_at.nil? 36 article.published_at = article.created_at if article.published_at.nil?
33 if article.reference_article && !article.parent 37 if article.reference_article && !article.parent
@@ -53,6 +57,10 @@ class Article &lt; ActiveRecord::Base @@ -53,6 +57,10 @@ class Article &lt; ActiveRecord::Base
53 URL_FORMAT = /\A(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\Z/ix 57 URL_FORMAT = /\A(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\Z/ix
54 58
55 validates_format_of :external_link, :with => URL_FORMAT, :if => lambda { |article| !article.external_link.blank? } 59 validates_format_of :external_link, :with => URL_FORMAT, :if => lambda { |article| !article.external_link.blank? }
  60 + validate :known_language
  61 + validate :used_translation
  62 + validate :native_translation_must_have_language
  63 + validate :translation_must_have_language
56 64
57 def is_trackable? 65 def is_trackable?
58 self.published? && self.notifiable? && self.advertise? 66 self.published? && self.notifiable? && self.advertise?
@@ -251,6 +259,65 @@ class Article &lt; ActiveRecord::Base @@ -251,6 +259,65 @@ class Article &lt; ActiveRecord::Base
251 false 259 false
252 end 260 end
253 261
  262 + named_scope :native_translations, :conditions => { :translation_of_id => nil }
  263 +
  264 + def translatable?
  265 + false
  266 + end
  267 +
  268 + def native_translation
  269 + self.translation_of.nil? ? self : self.translation_of
  270 + end
  271 +
  272 + def possible_translations
  273 + possibilities = Noosfero.locales.keys - self.native_translation.translations(:select => :language).map(&:language) - [self.native_translation.language]
  274 + possibilities << self.language unless self.language_changed?
  275 + possibilities
  276 + end
  277 +
  278 + def known_language
  279 + unless self.language.blank?
  280 + errors.add(:language, N_('Language not supported by Noosfero')) unless Noosfero.locales.key?(self.language)
  281 + end
  282 + end
  283 +
  284 + def used_translation
  285 + unless self.language.blank? or self.translation_of.nil?
  286 + errors.add(:language, N_('Language is already used')) unless self.possible_translations.include?(self.language)
  287 + end
  288 + end
  289 +
  290 + def translation_must_have_language
  291 + unless self.translation_of.nil?
  292 + errors.add(:language, N_('Language must be choosen')) if self.language.blank?
  293 + end
  294 + end
  295 +
  296 + def native_translation_must_have_language
  297 + unless self.translation_of.nil?
  298 + errors.add_to_base(N_('A language must be choosen for the native article')) if self.translation_of.language.blank?
  299 + end
  300 + end
  301 +
  302 + def rotate_translations
  303 + unless self.translations.empty?
  304 + rotate = self.translations
  305 + root = rotate.shift
  306 + root.update_attribute(:translation_of_id, nil)
  307 + root.translations = rotate
  308 + end
  309 + end
  310 +
  311 + def get_translation_to(locale)
  312 + if self.language.nil? || self.language == locale
  313 + self
  314 + elsif self.native_translation.language == locale
  315 + self.native_translation
  316 + else
  317 + self.native_translation.translations.first(:conditions => { :language => locale }) || self
  318 + end
  319 + end
  320 +
254 def published? 321 def published?
255 if self.published 322 if self.published
256 if self.parent && !self.parent.published? 323 if self.parent && !self.parent.published?
app/models/blog.rb
@@ -57,4 +57,8 @@ class Blog &lt; Folder @@ -57,4 +57,8 @@ class Blog &lt; Folder
57 settings_items :visualization_format, :type => :string, :default => 'full' 57 settings_items :visualization_format, :type => :string, :default => 'full'
58 validates_inclusion_of :visualization_format, :in => [ 'full', 'short' ], :if => :visualization_format 58 validates_inclusion_of :visualization_format, :in => [ 'full', 'short' ], :if => :visualization_format
59 59
  60 + settings_items :display_posts_in_current_language, :type => :boolean, :default => true
  61 +
  62 + alias :display_posts_in_current_language? :display_posts_in_current_language
  63 +
60 end 64 end
app/models/blog_archives_block.rb
@@ -24,7 +24,7 @@ class BlogArchivesBlock &lt; Block @@ -24,7 +24,7 @@ class BlogArchivesBlock &lt; Block
24 owner_blog = self.blog 24 owner_blog = self.blog
25 return nil unless owner_blog 25 return nil unless owner_blog
26 results = '' 26 results = ''
27 - owner_blog.posts.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year| 27 + owner_blog.posts.native_translations.group_by {|i| i.published_at.year }.sort_by { |year,count| -year }.each do |year, results_by_year|
28 results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})")) 28 results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})"))
29 results << "<ul class='#{year}-archive'>" 29 results << "<ul class='#{year}-archive'>"
30 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| 30 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|
app/models/environment.rb
@@ -108,7 +108,7 @@ class Environment &lt; ActiveRecord::Base @@ -108,7 +108,7 @@ class Environment &lt; ActiveRecord::Base
108 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), 108 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
109 'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'), 109 'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'),
110 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), 110 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'),
111 - 'xmpp_chat' => _('XMPP/Jabber based chat'), 111 + 'xmpp_chat' => _('XMPP/Jabber based chat')
112 } 112 }
113 end 113 end
114 114
app/models/event.rb
@@ -120,6 +120,10 @@ class Event &lt; Article @@ -120,6 +120,10 @@ class Event &lt; Article
120 true 120 true
121 end 121 end
122 122
  123 + def translatable?
  124 + true
  125 + end
  126 +
123 include MaybeAddHttp 127 include MaybeAddHttp
124 128
125 end 129 end
app/models/rss_feed.rb
@@ -62,7 +62,8 @@ class RssFeed &lt; Article @@ -62,7 +62,8 @@ class RssFeed &lt; Article
62 include ActionController::UrlWriter 62 include ActionController::UrlWriter
63 def fetch_articles 63 def fetch_articles
64 if parent && parent.has_posts? 64 if parent && parent.has_posts?
65 - return parent.posts.find(:all, :conditions => ['published = ?', true], :limit => self.limit, :order => 'id desc') 65 + language = self.language.blank? ? {} : { :language => self.language }
  66 + return parent.posts.find(:all, :conditions => { :published => true }.merge(language), :limit => self.limit, :order => 'id desc')
66 end 67 end
67 68
68 articles = 69 articles =
app/models/textile_article.rb
@@ -16,4 +16,8 @@ class TextileArticle &lt; TextArticle @@ -16,4 +16,8 @@ class TextileArticle &lt; TextArticle
16 true 16 true
17 end 17 end
18 18
  19 + def translatable?
  20 + true
  21 + end
  22 +
19 end 23 end
app/models/tiny_mce_article.rb
@@ -19,4 +19,8 @@ class TinyMceArticle &lt; TextArticle @@ -19,4 +19,8 @@ class TinyMceArticle &lt; TextArticle
19 true 19 true
20 end 20 end
21 21
  22 + def translatable?
  23 + true
  24 + end
  25 +
22 end 26 end
app/views/cms/_blog.rhtml
@@ -56,8 +56,11 @@ @@ -56,8 +56,11 @@
56 56
57 <%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %> 57 <%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %>
58 58
  59 +<%= labelled_check_box(_('Display posts in current language, if available'), 'article[display_posts_in_current_language]', '1', @article.display_posts_in_current_language?) %>
  60 +
59 <% f.fields_for 'feed', @article.feed do |feed| %> 61 <% f.fields_for 'feed', @article.feed do |feed| %>
60 <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %> 62 <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %>
  63 + <%= labelled_form_field(_('Include in RSS Feed only posts from language:'), feed.select(:language, { _('All') => nil }.merge(Noosfero.locales.invert))) %>
61 <% end %> 64 <% end %>
62 65
63 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> 66 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %>
app/views/cms/_event.rhtml
@@ -5,6 +5,8 @@ @@ -5,6 +5,8 @@
5 5
6 <%= required f.text_field('name', :size => '64') %> 6 <%= required f.text_field('name', :size => '64') %>
7 7
  8 +<%= render :partial => 'translatable' %>
  9 +
8 <%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %> 10 <%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %>
9 11
10 <%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %> 12 <%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %>
app/views/cms/_textile_article.rhtml
@@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
4 4
5 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %> 5 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
6 6
  7 +<%= render :partial => 'translatable' %>
  8 +
7 <br style="clear: both;"/> 9 <br style="clear: both;"/>
8 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %> 10 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
9 <em><%= _('Used when a short version of your text is needed.') %></em> 11 <em><%= _('Used when a short version of your text is needed.') %></em>
app/views/cms/_tiny_mce_article.rhtml
@@ -11,6 +11,8 @@ @@ -11,6 +11,8 @@
11 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %> 11 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
12 <% end %> 12 <% end %>
13 13
  14 + <%= render :partial => 'translatable' %>
  15 +
14 <br style="clear: both;"/> 16 <br style="clear: both;"/>
15 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %> 17 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
16 <em><%= _('Used when a short version of your text is needed.') %></em> 18 <em><%= _('Used when a short version of your text is needed.') %></em>
app/views/cms/_translatable.rhtml 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +<% if @article.translatable? %>
  2 + <div class="article-translation-field">
  3 + <%= label :article, :language, _('Language') %> <br />
  4 + <%= select :article, :language, @locales, { :selected => @selected_locale, :include_blank => true } %>
  5 + <%= hidden_field(:article, :translation_of_id) %>
  6 + </div>
  7 +<% end %>
app/views/content_viewer/view_page.rhtml
@@ -35,6 +35,11 @@ @@ -35,6 +35,11 @@
35 <% end %> 35 <% end %>
36 <% if !(profile.kind_of?(Enterprise) && environment.enabled?('disable_cms')) %> 36 <% if !(profile.kind_of?(Enterprise) && environment.enabled?('disable_cms')) %>
37 <% if !@page.gallery? %> 37 <% if !@page.gallery? %>
  38 + <%= link_to _('Add translation'),
  39 + profile.admin_url.merge(:controller => 'cms', :action => 'new',
  40 + :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)),
  41 + :type => @page.type, :article => { :translation_of_id => @page.native_translation.id }),
  42 + :class => 'button with-text icon-locale' if @page.translatable? && !@page.native_translation.language.blank? %>
38 <%= 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)))) %> 43 <%= 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)))) %>
39 <% end %> 44 <% end %>
40 <% if (@page.folder? && !@page.has_posts?) || (@page.parent && @page.parent.folder? && !@page.parent.has_posts?) %> 45 <% if (@page.folder? && !@page.has_posts?) || (@page.parent && @page.parent.folder? && !@page.parent.has_posts?) %>
@@ -50,6 +55,7 @@ @@ -50,6 +55,7 @@
50 <%= link_to content_tag( 'span', _('Suggest an article') ), profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}), :class => 'button with-text icon-new' %> 55 <%= link_to content_tag( 'span', _('Suggest an article') ), profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}), :class => 'button with-text icon-new' %>
51 <% end %> 56 <% end %>
52 <% end %> 57 <% end %>
  58 + <%= article_translations(@page) %>
53 <div id="article-header"> 59 <div id="article-header">
54 <%= link_to(image_tag('icons-mime/rss-feed.png'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %> 60 <%= link_to(image_tag('icons-mime/rss-feed.png'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %>
55 <%= article_title(@page, :no_link => true) %> 61 <%= article_title(@page, :no_link => true) %>
db/migrate/20101205034144_add_language_and_translation_of_id_to_article.rb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +class AddLanguageAndTranslationOfIdToArticle < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :articles, :translation_of_id, :interger
  4 + add_column :articles, :language, :string
  5 +
  6 + add_column :article_versions, :translation_of_id, :interger
  7 + add_column :article_versions, :language, :string
  8 +
  9 + add_index :articles, :translation_of_id
  10 +
  11 + select_all("select id, setting from articles where type = 'Blog'").each do |blog|
  12 + settings = YAML.load(blog['setting'])
  13 + settings[:display_posts_in_current_language] = true
  14 + assignments = ActiveRecord::Base.sanitize_sql_for_assignment(:setting => settings.to_yaml)
  15 + update("update articles set %s where id = %d" % [assignments, blog['id']])
  16 + end
  17 +
  18 + end
  19 +
  20 + def self.down
  21 + remove_index :articles, :translation_of_id
  22 +
  23 + remove_column :article_versions, :translation_of_id
  24 + remove_column :article_versions, :language
  25 +
  26 + remove_column :articles, :language
  27 + remove_column :articles, :translation_of_id
  28 + end
  29 +end
features/edit_article.feature
@@ -133,3 +133,32 @@ Feature: edit article @@ -133,3 +133,32 @@ Feature: edit article
133 Then I should be on "My new article" edit page 133 Then I should be on "My new article" edit page
134 And the "Title" field should contain "My new article" 134 And the "Title" field should contain "My new article"
135 And the "Text" field should contain "text for the new article" 135 And the "Text" field should contain "text for the new article"
  136 +
  137 + Scenario: add a translation to an article
  138 + Given I am on Joao Silva's sitemap
  139 + And I follow "Save the whales"
  140 + Then I should not see "Add translation"
  141 + And I follow "Edit"
  142 + And I select "English" from "Language"
  143 + Then I press "Save"
  144 + And I follow "Add translation"
  145 + And I fill in "Title" with "Mi neuvo artículo"
  146 + And I select "Español" from "Language"
  147 + When I press "Save"
  148 + Then I should be on /joaosilva/save-the-whales
  149 + And I should see "Translations"
  150 +
  151 + Scenario: not add a translation without a language
  152 + Given the following articles
  153 + | owner | name | language |
  154 + | joaosilva | Article in English | en |
  155 + And I am on Joao Silva's sitemap
  156 + And I follow "Article in English"
  157 + And I follow "Add translation"
  158 + And I fill in "Title" with "Article in Portuguese"
  159 + When I press "Save"
  160 + Then I should see "Language must be choosen"
  161 + And I select "Português" from "Language"
  162 + When I press "Save"
  163 + Then I should not see "Language must be choosen"
  164 + And I should be on /joaosilva/article-in-english
public/designs/icons/tango/style.css
@@ -75,3 +75,4 @@ @@ -75,3 +75,4 @@
75 .icon-reply { background-image: url(Tango/16x16/actions/mail-reply-sender.png) } 75 .icon-reply { background-image: url(Tango/16x16/actions/mail-reply-sender.png) }
76 .icon-newforum { background-image: url(Tango/16x16/apps/system-users.png) } 76 .icon-newforum { background-image: url(Tango/16x16/apps/system-users.png) }
77 .icon-newgallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } 77 .icon-newgallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) }
  78 +.icon-locale { background-image: url(Tango/16x16/apps/preferences-desktop-locale.png) }
78 \ No newline at end of file 79 \ No newline at end of file
public/stylesheets/application.css
@@ -5232,6 +5232,49 @@ h1#agenda-title { @@ -5232,6 +5232,49 @@ h1#agenda-title {
5232 margin-top: 10px; 5232 margin-top: 10px;
5233 } 5233 }
5234 5234
  5235 +
  5236 +.article-translations-menu {
  5237 + float: right;
  5238 + bottom: -15px;
  5239 +}
  5240 +
  5241 +.opera .article-translations-menu {
  5242 + bottom: -8px;
  5243 +}
  5244 +
  5245 +.webkit .article-translations-menu {
  5246 + bottom: -10px;
  5247 +}
  5248 +
  5249 +.article-translations-menu {
  5250 + text-decoration: none !important;
  5251 + height: 0;
  5252 +}
  5253 +
  5254 +.article-translations .menu-submenu-header,
  5255 +.article-translations .menu-submenu-content,
  5256 +.article-translations .menu-submenu-footer {
  5257 + background: none;
  5258 +}
  5259 +
  5260 +.article-translations .menu-submenu {
  5261 + bottom: auto;
  5262 + top: 60px;
  5263 + right: 0;
  5264 + border: 1px solid;
  5265 + background: #fff;
  5266 +}
  5267 +
  5268 +.msie7 .article-translations .menu-submenu,
  5269 +.webkit .article-translations .menu-submenu,
  5270 +.opera .article-translations .menu-submenu {
  5271 + top: 30px;
  5272 +}
  5273 +
  5274 +.article-translations .menu-submenu-list {
  5275 + list-style: none;
  5276 +}
  5277 +
5235 /* Forum */ 5278 /* Forum */
5236 5279
5237 .forum-posts .pagination { 5280 .forum-posts .pagination {
test/functional/application_controller_test.rb
@@ -419,4 +419,26 @@ class ApplicationControllerTest &lt; Test::Unit::TestCase @@ -419,4 +419,26 @@ class ApplicationControllerTest &lt; Test::Unit::TestCase
419 assert_tag :tag => 'meta', :attributes => { :name => 'description', :content => assigns(:environment).name } 419 assert_tag :tag => 'meta', :attributes => { :name => 'description', :content => assigns(:environment).name }
420 end 420 end
421 421
  422 + should 'set html lang as the article language if an article is present and has a language' do
  423 + a = fast_create(Article, :name => 'test article', :language => 'fr')
  424 + @controller.instance_variable_set('@page', a)
  425 + FastGettext.stubs(:locale).returns('es')
  426 + get :index
  427 + assert_tag :html, :attributes => { :lang => 'fr' }
  428 + end
  429 +
  430 + should 'set html lang as locale if no page present' do
  431 + FastGettext.stubs(:locale).returns('es')
  432 + get :index
  433 + assert_tag :html, :attributes => { :lang => 'es' }
  434 + end
  435 +
  436 + should 'set html lang as locale if page has no language' do
  437 + a = fast_create(Article, :name => 'test article', :language => nil)
  438 + @controller.instance_variable_set('@page', a)
  439 + FastGettext.stubs(:locale).returns('es')
  440 + get :index
  441 + assert_tag :html, :attributes => { :lang => 'es' }
  442 + end
  443 +
422 end 444 end
test/functional/cms_controller_test.rb
@@ -1440,4 +1440,63 @@ class CmsControllerTest &lt; Test::Unit::TestCase @@ -1440,4 +1440,63 @@ class CmsControllerTest &lt; Test::Unit::TestCase
1440 assert_response :success 1440 assert_response :success
1441 end 1441 end
1442 1442
  1443 + should 'article language should be selected' do
  1444 + textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'ru')
  1445 + get :edit, :profile => @profile.identifier, :id => textile.id
  1446 + assert_tag :option, :attributes => { :selected => 'selected', :value => 'ru' }, :parent => {
  1447 + :tag => 'select', :attributes => { :id => 'article_language'} }
  1448 + end
  1449 +
  1450 + should 'list possible languages and include blank option' do
  1451 + get :new, :profile => @profile.identifier, :type => 'TextileArticle'
  1452 + assert_equal Noosfero.locales.invert, assigns(:locales)
  1453 + assert_tag :option, :attributes => { :value => '' }, :parent => {
  1454 + :tag => 'select', :attributes => { :id => 'article_language'} }
  1455 + end
  1456 +
  1457 + should 'add translation to an article' do
  1458 + textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'ru')
  1459 + assert_difference Article, :count do
  1460 + post :new, :profile => @profile.identifier, :type => 'TextileArticle', :article => { :name => 'english translation', :translation_of_id => textile.id, :language => 'en' }
  1461 + end
  1462 + end
  1463 +
  1464 + should 'not display language selection if article is not translatable' do
  1465 + blog = fast_create(Blog, :name => 'blog', :profile_id => @profile.id)
  1466 + get :edit, :profile => @profile.identifier, :id => blog.id
  1467 + assert_no_tag :select, :attributes => { :id => 'article_language'}
  1468 + end
  1469 +
  1470 + should 'display display posts in current language input checked on edit blog' do
  1471 + get :new, :profile => profile.identifier, :type => 'Blog'
  1472 + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
  1473 + end
  1474 +
  1475 + should 'update to false blog display posts in current language setting' do
  1476 + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => true)
  1477 + post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :display_posts_in_current_language => false }
  1478 + profile.blog.reload
  1479 + assert !profile.blog.display_posts_in_current_language?
  1480 + end
  1481 +
  1482 + should 'update to true blog display posts in current language setting' do
  1483 + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => false)
  1484 + post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :display_posts_in_current_language => true }
  1485 + profile.blog.reload
  1486 + assert profile.blog.display_posts_in_current_language?
  1487 + end
  1488 +
  1489 + should 'be checked display posts in current language checkbox' do
  1490 + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => true)
  1491 + get :edit, :profile => profile.identifier, :id => profile.blog.id
  1492 + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
  1493 + end
  1494 +
  1495 + should 'be unchecked display posts in current language checkbox' do
  1496 + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile, :display_posts_in_current_language => false)
  1497 + get :edit, :profile => profile.identifier, :id => profile.blog.id
  1498 + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]' }
  1499 + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[display_posts_in_current_language]', :checked => 'checked' }
  1500 + end
  1501 +
1443 end 1502 end
test/functional/content_viewer_controller_test.rb
@@ -1095,4 +1095,147 @@ class ContentViewerControllerTest &lt; Test::Unit::TestCase @@ -1095,4 +1095,147 @@ class ContentViewerControllerTest &lt; Test::Unit::TestCase
1095 assert_tag :tag => 'div', :attributes => { :class => /main-block/ }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{forum.id}" }, :content => 'Configure forum' } 1095 assert_tag :tag => 'div', :attributes => { :class => /main-block/ }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{forum.id}" }, :content => 'Configure forum' }
1096 end 1096 end
1097 1097
  1098 + should 'display add translation link if article is translatable' do
  1099 + login_as @profile.identifier
  1100 + textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'en')
  1101 + get :view_page, :profile => @profile.identifier, :page => textile.explode_path
  1102 + assert_tag :a, :attributes => { :href => "/myprofile/#{profile.identifier}/cms/new?article%5Btranslation_of_id%5D=#{textile.id}&amp;type=#{TextileArticle}" }
  1103 + end
  1104 +
  1105 + should 'not display add translation link if article is not translatable' do
  1106 + login_as @profile.identifier
  1107 + blog = fast_create(Blog, :profile_id => @profile.id, :path => 'blog')
  1108 + get :view_page, :profile => @profile.identifier, :page => blog.explode_path
  1109 + assert_no_tag :a, :attributes => { :content => 'Add translation', :class => /icon-locale/ }
  1110 + end
  1111 +
  1112 + should 'not display add translation link if article hasnt a language defined' do
  1113 + login_as @profile.identifier
  1114 + textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile')
  1115 + get :view_page, :profile => @profile.identifier, :page => textile.explode_path
  1116 + assert_no_tag :a, :attributes => { :content => 'Add translation', :class => /icon-locale/ }
  1117 + end
  1118 +
  1119 + should 'diplay translations link if article has translations' do
  1120 + login_as @profile.identifier
  1121 + textile = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'textile', :language => 'en')
  1122 + translation = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'translation', :language => 'es', :translation_of_id => textile)
  1123 + get :view_page, :profile => @profile.identifier, :page => textile.explode_path
  1124 + assert_tag :a, :attributes => { :class => /article-translations-menu/, :onclick => /toggleSubmenu/ }
  1125 + end
  1126 +
  1127 + should 'be redirected to translation if article is a root' do
  1128 + @request.env['HTTP_REFERER'] = 'http://some.path'
  1129 + FastGettext.stubs(:locale).returns('es')
  1130 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1131 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1132 + get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
  1133 + assert_redirected_to :profile => @profile.identifier, :page => es_article.explode_path
  1134 + assert_equal es_article, assigns(:page)
  1135 + end
  1136 +
  1137 + should 'be redirected to translation' do
  1138 + @request.env['HTTP_REFERER'] = 'http://some.path'
  1139 + FastGettext.stubs(:locale).returns('en')
  1140 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1141 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1142 + get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
  1143 + assert_redirected_to :profile => @profile.identifier, :page => en_article.explode_path
  1144 + assert_equal en_article, assigns(:page)
  1145 + end
  1146 +
  1147 + should 'not be redirected if already in translation' do
  1148 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1149 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1150 + @request.env['HTTP_REFERER'] = "http://localhost:3000/#{@profile.identifier}/#{es_article.path}"
  1151 + FastGettext.stubs(:locale).returns('es')
  1152 + get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
  1153 + assert_response :success
  1154 + assert_equal es_article, assigns(:page)
  1155 + end
  1156 +
  1157 + should 'not be redirected if article does not have a language' do
  1158 + FastGettext.stubs(:locale).returns('es')
  1159 + article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'article')
  1160 + get :view_page, :profile => @profile.identifier, :page => article.explode_path
  1161 + assert_response :success
  1162 + assert_equal article, assigns(:page)
  1163 + end
  1164 +
  1165 + should 'not be redirected if http_referer is a translation' do
  1166 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1167 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1168 + @request.env['HTTP_REFERER'] = "http://localhost:3000/#{@profile.identifier}/#{es_article.path}"
  1169 + FastGettext.stubs(:locale).returns('es')
  1170 + get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
  1171 + assert_response :success
  1172 + assert_equal en_article, assigns(:page)
  1173 + end
  1174 +
  1175 + should 'be redirected if http_referer is nil' do
  1176 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1177 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1178 + @request.env['HTTP_REFERER'] = nil
  1179 + FastGettext.stubs(:locale).returns('es')
  1180 + get :view_page, :profile => @profile.identifier, :page => en_article.explode_path
  1181 + assert_redirected_to :profile => @profile.identifier, :page => es_article.explode_path
  1182 + assert_equal es_article, assigns(:page)
  1183 + end
  1184 +
  1185 + should 'not be redirected to transition if came from edit' do
  1186 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1187 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1188 + FastGettext.stubs(:locale).returns('es')
  1189 + @request.env['HTTP_REFERER'] = "http://localhost/myprofile/#{@profile.identifier}/cms/edit/#{en_article.id}"
  1190 + get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
  1191 + assert_response :success
  1192 + assert_equal es_article, assigns(:page)
  1193 + end
  1194 +
  1195 + should 'not be redirected to transition if came from new' do
  1196 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1197 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1198 + FastGettext.stubs(:locale).returns('es')
  1199 + @request.env['HTTP_REFERER'] = "http://localhost/myprofile/#{@profile.identifier}/cms/new"
  1200 + get :view_page, :profile => @profile.identifier, :page => es_article.explode_path
  1201 + assert_response :success
  1202 + assert_equal es_article, assigns(:page)
  1203 + end
  1204 +
  1205 + should 'replace article for his translation at blog listing if blog option is enabled' do
  1206 + FastGettext.stubs(:locale).returns('es')
  1207 + blog = fast_create(Blog, :profile_id => profile.id, :path => 'blog')
  1208 + blog.stubs(:display_posts_in_current_language).returns(true)
  1209 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1210 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1211 + blog.posts = [en_article, es_article]
  1212 +
  1213 + get :view_page, :profile => @profile.identifier, :page => blog.explode_path
  1214 + assert_tag :div, :attributes => { :id => "post-#{es_article.id}" }
  1215 + assert_no_tag :div, :attributes => { :id => "post-#{en_article.id}" }
  1216 + end
  1217 +
  1218 + should 'list all posts at blog listing if blog option is disabled' do
  1219 + FastGettext.stubs(:locale).returns('es')
  1220 + blog = Blog.create!(:name => 'A blog test', :profile => profile, :display_posts_in_current_language => false)
  1221 + blog.posts << es_post = TextileArticle.create!(:name => 'Spanish Post', :profile => profile, :parent => blog, :language => 'es')
  1222 + blog.posts << en_post = TextileArticle.create!(:name => 'English Post', :profile => profile, :parent => blog, :language => 'en', :translation_of_id => es_post.id)
  1223 + get :view_page, :profile => profile.identifier, :page => [blog.path]
  1224 + assert_equal 2, assigns(:posts).size
  1225 + assert_tag :div, :attributes => { :id => "post-#{es_post.id}" }
  1226 + assert_tag :div, :attributes => { :id => "post-#{en_post.id}" }
  1227 + end
  1228 +
  1229 + should 'display only native translations at blog listing if blog option is enabled' do
  1230 + FastGettext.stubs(:locale).returns('es')
  1231 + blog = fast_create(Blog, :profile_id => profile.id, :path => 'blog')
  1232 + blog.stubs(:display_posts_in_current_language).returns(true)
  1233 + en_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'en_article', :language => 'en')
  1234 + es_article = fast_create(TextileArticle, :profile_id => @profile.id, :path => 'es_article', :language => 'es', :translation_of_id => en_article)
  1235 + blog.posts = [en_article, es_article]
  1236 +
  1237 + get :view_page, :profile => @profile.identifier, :page => blog.explode_path
  1238 + assert_equal [es_article], assigns(:posts)
  1239 + end
  1240 +
1098 end 1241 end
test/unit/article_test.rb
@@ -1229,4 +1229,174 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -1229,4 +1229,174 @@ class ArticleTest &lt; Test::Unit::TestCase
1229 assert_equal [g], p.articles.galleries 1229 assert_equal [g], p.articles.galleries
1230 end 1230 end
1231 1231
  1232 + should 'has many translations' do
  1233 + a = build(Article)
  1234 + assert_raises(ActiveRecord::AssociationTypeMismatch) { a.translations << 1 }
  1235 + assert_nothing_raised { a.translations << build(Article) }
  1236 + end
  1237 +
  1238 + should 'belongs to translation of' do
  1239 + a = build(Article)
  1240 + assert_raises(ActiveRecord::AssociationTypeMismatch) { a.translation_of = 1 }
  1241 + assert_nothing_raised { a.translation_of = build(Article) }
  1242 + end
  1243 +
  1244 + should 'has language' do
  1245 + a = build(Article)
  1246 + assert_nothing_raised { a.language = 'en' }
  1247 + end
  1248 +
  1249 + should 'validade inclusion of language' do
  1250 + a = build(Article)
  1251 + a.language = '12'
  1252 + a.valid?
  1253 + assert a.errors.invalid?(:language)
  1254 + a.language = 'en'
  1255 + a.valid?
  1256 + assert !a.errors.invalid?(:language)
  1257 + end
  1258 +
  1259 + should 'language can be blank' do
  1260 + a = build(Article)
  1261 + a.valid?
  1262 + assert !a.errors.invalid?(:language)
  1263 + a.language = ''
  1264 + a.valid?
  1265 + assert !a.errors.invalid?(:language)
  1266 + end
  1267 +
  1268 + should 'article is not translatable' do
  1269 + a = build(Article)
  1270 + assert !a.translatable?
  1271 + end
  1272 +
  1273 + should 'get native translation' do
  1274 + native_article = fast_create(Article)
  1275 + article_translation = fast_create(Article)
  1276 + native_article.translations << article_translation
  1277 + assert_equal native_article, native_article.native_translation
  1278 + assert_equal native_article, article_translation.native_translation
  1279 + end
  1280 +
  1281 + should 'list possible translations' do
  1282 + native_article = fast_create(Article, :language => 'pt')
  1283 + article_translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id)
  1284 + possible_translations = native_article.possible_translations
  1285 + assert !possible_translations.include?('en')
  1286 + assert possible_translations.include?('pt')
  1287 + end
  1288 +
  1289 + should 'verify if translation is already in use' do
  1290 + native_article = fast_create(Article, :language => 'pt')
  1291 + article_translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id)
  1292 + a = build(Article)
  1293 + a.language = 'en'
  1294 + a.translation_of = native_article
  1295 + a.valid?
  1296 + assert a.errors.invalid?(:language)
  1297 + a.language = 'es'
  1298 + a.valid?
  1299 + assert !a.errors.invalid?(:language)
  1300 + end
  1301 +
  1302 + should 'verify if native translation is already in use' do
  1303 + native_article = fast_create(Article, :language => 'pt')
  1304 + a = build(Article)
  1305 + a.language = 'pt'
  1306 + a.translation_of = native_article
  1307 + a.valid?
  1308 + assert a.errors.invalid?(:language)
  1309 + a.language = 'es'
  1310 + a.valid?
  1311 + assert !a.errors.invalid?(:language)
  1312 + end
  1313 +
  1314 + should 'translation have a language' do
  1315 + native_article = fast_create(Article, :language => 'pt')
  1316 + a = build(Article)
  1317 + a.translation_of = native_article
  1318 + a.valid?
  1319 + assert a.errors.invalid?(:language)
  1320 + a.language = 'en'
  1321 + a.valid?
  1322 + assert !a.errors.invalid?(:language)
  1323 + end
  1324 +
  1325 + should 'native translation have a language' do
  1326 + native_article = fast_create(Article)
  1327 + a = build(Article)
  1328 + a.language = 'en'
  1329 + a.translation_of = native_article
  1330 + a.valid?
  1331 + n = a.errors.count
  1332 + native_article.language = 'pt'
  1333 + native_article.save
  1334 + a.valid?
  1335 + assert_equal n - 1, a.errors.count
  1336 + end
  1337 +
  1338 + should 'rotate translations when root article is destroyed' do
  1339 + native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1340 + translation1 = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
  1341 + translation2 = fast_create(Article, :language => 'es', :translation_of_id => native_article.id, :profile_id => @profile.id)
  1342 + native_article.destroy
  1343 + assert translation1.translation_of.nil?
  1344 + assert translation1.translations.include?(translation2)
  1345 + end
  1346 +
  1347 + should 'rotate one translation when root article is destroyed' do
  1348 + native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1349 + translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
  1350 + native_article.destroy
  1351 + assert translation.translation_of.nil?
  1352 + assert translation.translations.empty?
  1353 + end
  1354 +
  1355 + should 'get self if article does not a language' do
  1356 + article = fast_create(Article, :profile_id => @profile.id)
  1357 + assert_equal article, article.get_translation_to('en')
  1358 + end
  1359 +
  1360 + should 'get self if article is the translation' do
  1361 + article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1362 + assert_equal article, article.get_translation_to('pt')
  1363 + end
  1364 +
  1365 + should 'get the native translation if it is the translation' do
  1366 + native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1367 + translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
  1368 + assert_equal native_article, translation.get_translation_to('pt')
  1369 + end
  1370 +
  1371 + should 'get the translation if article has translation' do
  1372 + native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1373 + translation = fast_create(Article, :language => 'en', :translation_of_id => native_article.id, :profile_id => @profile.id)
  1374 + assert_equal translation, native_article.get_translation_to('en')
  1375 + end
  1376 +
  1377 + should 'get self if article does not has a translation' do
  1378 + native_article = fast_create(Article, :language => 'pt', :profile_id => @profile.id)
  1379 + assert_equal native_article, native_article.get_translation_to('en')
  1380 + end
  1381 +
  1382 + should 'get only non translated articles' do
  1383 + p = fast_create(Profile)
  1384 + native = fast_create(Article, :language => 'pt', :profile_id => p.id)
  1385 + translation = fast_create(Article, :language => 'en', :translation_of_id => native.id, :profile_id => p.id)
  1386 + assert_equal [native], p.articles.native_translations
  1387 + end
  1388 +
  1389 + should 'not list own language as a possible translation if language has changed' do
  1390 + a = build(Article, :language => 'pt')
  1391 + assert !a.possible_translations.include?('pt')
  1392 + a = fast_create(Article, :language => 'pt')
  1393 + a.language = 'en'
  1394 + assert !a.possible_translations.include?('en')
  1395 + end
  1396 +
  1397 + should 'list own language as a possible translation if language has not changed' do
  1398 + a = fast_create(Article, :language => 'pt')
  1399 + assert a.possible_translations.include?('pt')
  1400 + end
  1401 +
1232 end 1402 end
test/unit/blog_archives_block_test.rb
@@ -102,4 +102,36 @@ class BlogArchivesBlockTest &lt; ActiveSupport::TestCase @@ -102,4 +102,36 @@ class BlogArchivesBlockTest &lt; ActiveSupport::TestCase
102 assert_no_match(/blog-two/m, block.content) 102 assert_no_match(/blog-two/m, block.content)
103 end 103 end
104 104
  105 + should 'list amount native posts by year' do
  106 + date = DateTime.parse('2008-01-01')
  107 + blog = profile.blog
  108 + 2.times do |i|
  109 + post = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
  110 + :parent_id => blog.id, :language => 'en')
  111 + post.update_attribute(:published_at, date)
  112 + translation = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
  113 + :parent_id => blog.id, :language => 'en', :translation_of_id => post.id)
  114 + translation.update_attribute(:published_at, date)
  115 + end
  116 + block = BlogArchivesBlock.new
  117 + block.stubs(:owner).returns(profile)
  118 + assert_tag_in_string block.content, :tag => 'li', :content => '2008 (2)'
  119 + end
  120 +
  121 + should 'list amount native posts by month' do
  122 + date = DateTime.parse('2008-01-01')
  123 + blog = profile.blog
  124 + 2.times do |i|
  125 + post = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
  126 + :parent_id => blog.id, :language => 'en')
  127 + post.update_attribute(:published_at, date)
  128 + translation = fast_create(TextileArticle, :name => "post #{i} test", :profile_id => profile.id,
  129 + :parent_id => blog.id, :language => 'en', :translation_of_id => post.id)
  130 + translation.update_attribute(:published_at, date)
  131 + end
  132 + block = BlogArchivesBlock.new
  133 + block.stubs(:owner).returns(profile)
  134 + assert_tag_in_string block.content, :tag => 'a', :content => 'January (2)', :attributes => {:href => /^http:\/\/.*\/flatline\/blog-one\?month=01&year=2008$/ }
  135 + end
  136 +
105 end 137 end
test/unit/blog_test.rb
@@ -175,4 +175,20 @@ class BlogTest &lt; ActiveSupport::TestCase @@ -175,4 +175,20 @@ class BlogTest &lt; ActiveSupport::TestCase
175 assert Blog.new.has_posts? 175 assert Blog.new.has_posts?
176 end 176 end
177 177
  178 + should 'display posts in current language by default' do
  179 + blog = Blog.new
  180 + assert blog.display_posts_in_current_language
  181 + assert blog.display_posts_in_current_language?
  182 + end
  183 +
  184 + should 'update display posts in current language setting' do
  185 + p = create_user('testuser').person
  186 + p.articles << Blog.new(:profile => p, :name => 'Blog test')
  187 + blog = p.blog
  188 + blog.display_posts_in_current_language = false
  189 + assert blog.save! && blog.reload
  190 + assert !blog.reload.display_posts_in_current_language
  191 + assert !blog.reload.display_posts_in_current_language?
  192 + end
  193 +
178 end 194 end
test/unit/event_test.rb
@@ -265,4 +265,10 @@ class EventTest &lt; ActiveSupport::TestCase @@ -265,4 +265,10 @@ class EventTest &lt; ActiveSupport::TestCase
265 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.description 265 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.description
266 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.address 266 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.address
267 end 267 end
  268 +
  269 + should 'be translatable' do
  270 + e = Event.new
  271 + assert e.translatable?
  272 + end
  273 +
268 end 274 end
test/unit/rss_feed_test.rb
@@ -240,4 +240,24 @@ class RssFeedTest &lt; Test::Unit::TestCase @@ -240,4 +240,24 @@ class RssFeedTest &lt; Test::Unit::TestCase
240 assert_not_nil RssFeed.new.to_html 240 assert_not_nil RssFeed.new.to_html
241 end 241 end
242 242
  243 + should 'include posts from all languages' do
  244 + profile = create_user('testuser').person
  245 + blog = Blog.create!(:name => 'blog-test', :profile => profile, :language => nil)
  246 + blog.posts << en_post = fast_create(TextArticle, :name => "English", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'en')
  247 + blog.posts << es_post = fast_create(TextArticle, :name => "Spanish", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'es')
  248 +
  249 + assert blog.feed.fetch_articles.include?(en_post)
  250 + assert blog.feed.fetch_articles.include?(es_post)
  251 + end
  252 +
  253 + should 'include only posts from some language' do
  254 + profile = create_user('testuser').person
  255 + blog = Blog.create!(:name => 'blog-test', :profile => profile)
  256 + blog.feed.update_attributes! :language => 'es'
  257 + blog.posts << en_post = fast_create(TextArticle, :name => "English", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'en')
  258 + blog.posts << es_post = fast_create(TextArticle, :name => "Spanish", :profile_id => profile.id, :parent_id => blog.id, :published => true, :language => 'es')
  259 +
  260 + assert_equal [es_post], blog.feed.fetch_articles
  261 + end
  262 +
243 end 263 end
test/unit/textile_article_test.rb
@@ -146,4 +146,9 @@ class TextileArticleTest &lt; Test::Unit::TestCase @@ -146,4 +146,9 @@ class TextileArticleTest &lt; Test::Unit::TestCase
146 assert_equal false, a.is_trackable? 146 assert_equal false, a.is_trackable?
147 end 147 end
148 148
  149 + should 'be translatable' do
  150 + a = TextileArticle.new
  151 + assert a.translatable?
  152 + end
  153 +
149 end 154 end
test/unit/tiny_mce_article_test.rb
@@ -237,5 +237,8 @@ class TinyMceArticleTest &lt; Test::Unit::TestCase @@ -237,5 +237,8 @@ class TinyMceArticleTest &lt; Test::Unit::TestCase
237 assert_equal false, a.is_trackable? 237 assert_equal false, a.is_trackable?
238 end 238 end
239 239
240 - 240 + should 'be translatable' do
  241 + a = TinyMceArticle.new
  242 + assert a.translatable?
  243 + end
241 end 244 end