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 89 @article = profile.articles.find(params[:id])
90 90 @parent_id = params[:parent_id]
91 91 @type = params[:type] || @article.class.to_s
  92 + translations if @article.translatable?
92 93 continue = params[:continue]
93 94  
94 95 refuse_blocks
... ... @@ -138,6 +139,8 @@ class CmsController < MyProfileController
138 139 @parent_id = parent.id
139 140 end
140 141  
  142 + translations if @article.translatable?
  143 +
141 144 @article.profile = profile
142 145 @article.last_changed_by = user
143 146  
... ... @@ -367,5 +370,11 @@ class CmsController < MyProfileController
367 370 def per_page
368 371 10
369 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 379 end
371 380  
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -51,6 +51,8 @@ class ContentViewerController < ApplicationController
51 51 return
52 52 end
53 53  
  54 + redirect_to_translation
  55 +
54 56 # At this point the page will be showed
55 57 @page.hit
56 58  
... ... @@ -85,7 +87,11 @@ class ContentViewerController < ApplicationController
85 87 @page.posts
86 88 end
87 89  
  90 + posts = posts.native_translations if @page.blog? && @page.display_posts_in_current_language?
  91 +
88 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 95 end
90 96  
91 97 if @page.folder? && @page.gallery?
... ... @@ -125,4 +131,24 @@ class ContentViewerController < ApplicationController
125 131 def per_page
126 132 12
127 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 154 end
... ...
app/helpers/application_helper.rb
... ... @@ -27,7 +27,7 @@ module ApplicationHelper
27 27 include AccountHelper
28 28  
29 29 def locale
30   - FastGettext.locale
  30 + (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
31 31 end
32 32  
33 33 def load_web2_conf
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -35,4 +35,16 @@ module ContentViewerHelper
35 35 text && (text.first(40) + (text.size > 40 ? '…' : ''))
36 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 50 end
... ...
app/models/article.rb
... ... @@ -28,6 +28,10 @@ class Article &lt; ActiveRecord::Base
28 28  
29 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 35 before_create do |article|
32 36 article.published_at = article.created_at if article.published_at.nil?
33 37 if article.reference_article && !article.parent
... ... @@ -53,6 +57,10 @@ class Article &lt; ActiveRecord::Base
53 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 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 65 def is_trackable?
58 66 self.published? && self.notifiable? && self.advertise?
... ... @@ -251,6 +259,65 @@ class Article &lt; ActiveRecord::Base
251 259 false
252 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 321 def published?
255 322 if self.published
256 323 if self.parent && !self.parent.published?
... ...
app/models/blog.rb
... ... @@ -57,4 +57,8 @@ class Blog &lt; Folder
57 57 settings_items :visualization_format, :type => :string, :default => 'full'
58 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 64 end
... ...
app/models/blog_archives_block.rb
... ... @@ -24,7 +24,7 @@ class BlogArchivesBlock &lt; Block
24 24 owner_blog = self.blog
25 25 return nil unless owner_blog
26 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 28 results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})"))
29 29 results << "<ul class='#{year}-archive'>"
30 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 108 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
109 109 'enterprises_are_disabled_when_created' => __('Enterprises are disabled when created'),
110 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 113 end
114 114  
... ...
app/models/event.rb
... ... @@ -120,6 +120,10 @@ class Event &lt; Article
120 120 true
121 121 end
122 122  
  123 + def translatable?
  124 + true
  125 + end
  126 +
123 127 include MaybeAddHttp
124 128  
125 129 end
... ...
app/models/rss_feed.rb
... ... @@ -62,7 +62,8 @@ class RssFeed &lt; Article
62 62 include ActionController::UrlWriter
63 63 def fetch_articles
64 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 67 end
67 68  
68 69 articles =
... ...
app/models/textile_article.rb
... ... @@ -16,4 +16,8 @@ class TextileArticle &lt; TextArticle
16 16 true
17 17 end
18 18  
  19 + def translatable?
  20 + true
  21 + end
  22 +
19 23 end
... ...
app/models/tiny_mce_article.rb
... ... @@ -19,4 +19,8 @@ class TinyMceArticle &lt; TextArticle
19 19 true
20 20 end
21 21  
  22 + def translatable?
  23 + true
  24 + end
  25 +
22 26 end
... ...
app/views/cms/_blog.rhtml
... ... @@ -56,8 +56,11 @@
56 56  
57 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 61 <% f.fields_for 'feed', @article.feed do |feed| %>
60 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 64 <% end %>
62 65  
63 66 <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %>
... ...
app/views/cms/_event.rhtml
... ... @@ -5,6 +5,8 @@
5 5  
6 6 <%= required f.text_field('name', :size => '64') %>
7 7  
  8 +<%= render :partial => 'translatable' %>
  9 +
8 10 <%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %>
9 11  
10 12 <%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %>
... ...
app/views/cms/_textile_article.rhtml
... ... @@ -4,6 +4,8 @@
4 4  
5 5 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
6 6  
  7 +<%= render :partial => 'translatable' %>
  8 +
7 9 <br style="clear: both;"/>
8 10 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
9 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 11 <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64')) %>
12 12 <% end %>
13 13  
  14 + <%= render :partial => 'translatable' %>
  15 +
14 16 <br style="clear: both;"/>
15 17 <%= button :add, _("Lead"), '#', :id => "lead-button", :style => "margin-left: 0px;" %>
16 18 <em><%= _('Used when a short version of your text is needed.') %></em>
... ...
app/views/cms/_translatable.rhtml 0 → 100644
... ... @@ -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 35 <% end %>
36 36 <% if !(profile.kind_of?(Enterprise) && environment.enabled?('disable_cms')) %>
37 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 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 44 <% end %>
40 45 <% if (@page.folder? && !@page.has_posts?) || (@page.parent && @page.parent.folder? && !@page.parent.has_posts?) %>
... ... @@ -50,6 +55,7 @@
50 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 56 <% end %>
52 57 <% end %>
  58 + <%= article_translations(@page) %>
53 59 <div id="article-header">
54 60 <%= link_to(image_tag('icons-mime/rss-feed.png'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %>
55 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 @@
  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 133 Then I should be on "My new article" edit page
134 134 And the "Title" field should contain "My new article"
135 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 75 .icon-reply { background-image: url(Tango/16x16/actions/mail-reply-sender.png) }
76 76 .icon-newforum { background-image: url(Tango/16x16/apps/system-users.png) }
77 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 79 \ No newline at end of file
... ...
public/stylesheets/application.css
... ... @@ -5232,6 +5232,49 @@ h1#agenda-title {
5232 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 5278 /* Forum */
5236 5279  
5237 5280 .forum-posts .pagination {
... ...
test/functional/application_controller_test.rb
... ... @@ -419,4 +419,26 @@ class ApplicationControllerTest &lt; Test::Unit::TestCase
419 419 assert_tag :tag => 'meta', :attributes => { :name => 'description', :content => assigns(:environment).name }
420 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 444 end
... ...
test/functional/cms_controller_test.rb
... ... @@ -1440,4 +1440,63 @@ class CmsControllerTest &lt; Test::Unit::TestCase
1440 1440 assert_response :success
1441 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 1502 end
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -1095,4 +1095,147 @@ class ContentViewerControllerTest &lt; Test::Unit::TestCase
1095 1095 assert_tag :tag => 'div', :attributes => { :class => /main-block/ }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{forum.id}" }, :content => 'Configure forum' }
1096 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 1241 end
... ...
test/unit/article_test.rb
... ... @@ -1229,4 +1229,174 @@ class ArticleTest &lt; Test::Unit::TestCase
1229 1229 assert_equal [g], p.articles.galleries
1230 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 1402 end
... ...
test/unit/blog_archives_block_test.rb
... ... @@ -102,4 +102,36 @@ class BlogArchivesBlockTest &lt; ActiveSupport::TestCase
102 102 assert_no_match(/blog-two/m, block.content)
103 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 137 end
... ...
test/unit/blog_test.rb
... ... @@ -175,4 +175,20 @@ class BlogTest &lt; ActiveSupport::TestCase
175 175 assert Blog.new.has_posts?
176 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 194 end
... ...
test/unit/event_test.rb
... ... @@ -265,4 +265,10 @@ class EventTest &lt; ActiveSupport::TestCase
265 265 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.description
266 266 assert_match /<!-- .* --> <h1> Wellformed html code <\/h1>/, event.address
267 267 end
  268 +
  269 + should 'be translatable' do
  270 + e = Event.new
  271 + assert e.translatable?
  272 + end
  273 +
268 274 end
... ...
test/unit/rss_feed_test.rb
... ... @@ -240,4 +240,24 @@ class RssFeedTest &lt; Test::Unit::TestCase
240 240 assert_not_nil RssFeed.new.to_html
241 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 263 end
... ...
test/unit/textile_article_test.rb
... ... @@ -146,4 +146,9 @@ class TextileArticleTest &lt; Test::Unit::TestCase
146 146 assert_equal false, a.is_trackable?
147 147 end
148 148  
  149 + should 'be translatable' do
  150 + a = TextileArticle.new
  151 + assert a.translatable?
  152 + end
  153 +
149 154 end
... ...
test/unit/tiny_mce_article_test.rb
... ... @@ -237,5 +237,8 @@ class TinyMceArticleTest &lt; Test::Unit::TestCase
237 237 assert_equal false, a.is_trackable?
238 238 end
239 239  
240   -
  240 + should 'be translatable' do
  241 + a = TinyMceArticle.new
  242 + assert a.translatable?
  243 + end
241 244 end
... ...