Merge branch '619-gender-aware-translations' of https://github.com/siefca/diaspora into siefca-619-gender-aware-translations

This commit is contained in:
Hexagon 2010-12-01 19:13:39 +01:00
commit 5651ea1d33
22 changed files with 269 additions and 43 deletions

View file

@ -30,9 +30,9 @@ class InvitationsController < Devise::InvitationsController
if e.message == "You have no invites"
flash[:error] = I18n.t 'invitations.create.no_more'
elsif e.message == "You already invited this person"
flash[:error] = I18n.t 'invitations.create.already_sent'
flash[:error] = I18n.t('invitations.create.already_sent', :gender => current_user)
elsif e.message == "You are already connected to this person"
flash[:error] = I18n.t 'invitations.create.already_contacts'
flash[:error] = I18n.t('invitations.create.already_contacts', :gender => current_user)
else
raise e
end

View file

@ -55,15 +55,15 @@ class PhotosController < ApplicationController
end
rescue TypeError
message = I18n.t 'photos.create.type_error'
message = I18n.t('photos.create.type_error', :gender => current_user)
respond_with :location => photos_path, :error => message
rescue CarrierWave::IntegrityError
message = I18n.t 'photos.create.integrity_error'
message = I18n.t('photos.create.integrity_error', :gender => current_user)
respond_with :location => photos_path, :error => message
rescue RuntimeError => e
message = I18n.t 'photos.create.runtime_error'
message = I18n.t('photos.create.runtime_error', :gender => current_user)
respond_with :location => photos_path, :error => message
raise e
end

View file

@ -45,7 +45,7 @@ class RequestsController < ApplicationController
:into => aspect)
if @request.save
current_user.dispatch_request(@request)
flash.now[:notice] = I18n.t('requests.create.sent')
flash.now[:notice] = I18n.t('requests.create.sent', :gender => current_user)
redirect_to :back
else
flash.now[:error] = @request.errors.full_messages.join(', ')

View file

@ -28,6 +28,7 @@ class UsersController < ApplicationController
params[:user].delete(:password) if params[:user][:password].blank?
params[:user].delete(:password_confirmation) if params[:user][:password].blank? and params[:user][:password_confirmation].blank?
params[:user].delete(:language) if params[:user][:language].blank?
params[:user].delete(:grammatical_gender) if params[:user][:grammatical_gender].blank?
if params[:user][:password] && params[:user][:password_confirmation]
if @user.update_attributes(:password => params[:user][:password], :password_confirmation => params[:user][:password_confirmation])
@ -37,10 +38,18 @@ class UsersController < ApplicationController
end
elsif params[:user][:language]
if @user.update_attributes(:language => params[:user][:language])
if params[:user][:grammatical_gender]
if @user.update_attributes(:grammatical_gender => params[:user][:grammatical_gender])
flash[:notice] = I18n.t 'users.update.language_changed'
else
flash[:error] = I18n.t 'users.update.language_not_changed'
end
else
flash[:notice] = I18n.t 'users.update.language_changed'
end
else
flash[:error] = I18n.t 'users.update.language_not_changed'
end
end
redirect_to edit_user_path(@user)
@ -80,6 +89,7 @@ class UsersController < ApplicationController
@step ||= 1
if @step == 4
@user.grammatical_gender = I18n::Backend::Genderize.guess(@profile.gender)
@user.getting_started = false
@user.save
end

View file

@ -9,7 +9,7 @@ module AspectsHelper
def remove_link( aspect )
if aspect.contacts.size == 0
link_to I18n.t('aspects.helper.remove'), aspect, :method => :delete, :confirm => I18n.t('aspects.helper.are_you_sure')
link_to I18n.t('aspects.helper.remove'), aspect, :method => :delete, :confirm => I18n.t('aspects.helper.are_you_sure', :gender => current_user)
else
"<span class='grey' title=#{I18n.t('aspects.helper.aspect_not_empty')}>#{I18n.t('aspects.helper.remove')}</span>"
end

View file

@ -6,4 +6,30 @@ module LanguageHelper
end
options.sort_by { |o| o[0] }
end
def options_for_gender_select
grammatical_gender = current_user.grammatical_gender
genders_list = I18n::Backend::Genderize.known_genders.map do |gender|
[t(".#{gender}"), gender]
end
if grammatical_gender.blank?
grammatical_gender = I18n::Backend::Genderize.guess(user.profile.gender)
end
options_for_select(genders_list, grammatical_gender.to_s)
end
def gender_select_disabled
not I18n::Backend::Genderize.supports?(current_user.language)
end
def grammatical_gender_languages
@glang_cache ||= array_or_string_for_javascript(I18n::Backend::Genderize::SupportedLanguages)
end
def options_for_grammatical_gender_block
enabled = I18n::Backend::Genderize.supports? current_user.language
{:style => 'display: ' + (enabled ? 'inline' : 'none') + ';' +
' margin-left: 1em; margin-right: 0.5em;'
}
end
end

View file

@ -22,7 +22,7 @@ module PeopleHelper
def action_link(person, is_contact)
if is_contact
link_to t('people.profile_sidebar.remove_contact'), person, :confirm => t('are_you_sure'), :method => :delete
link_to t('people.profile_sidebar.remove_contact'), person, :confirm => t('are_you_sure', :gender => current_user), :method => :delete
elsif person == current_user.person
link_to t('people.profile_sidebar.edit_my_profile'), edit_person_path(person)
end

View file

@ -29,6 +29,7 @@ class User
key :getting_started, Boolean, :default => true
key :language, String
key :grammatical_gender, String
before_validation :strip_and_downcase_username, :on => :create
before_validation :set_current_language, :on => :create
@ -38,6 +39,7 @@ class User
validates_format_of :username, :with => /\A[A-Za-z0-9_.]+\z/
validates_length_of :username, :maximum => 32
validates_inclusion_of :language, :in => AVAILABLE_LANGUAGE_CODES
validates_inclusion_of :grammatical_gender, :in => I18n::Backend::Genderize::known_genders + [nil]
validates_presence_of :person, :unless => proc {|user| user.invitation_token.present?}
validates_associated :person
@ -61,7 +63,7 @@ class User
person.save if person
end
attr_accessible :getting_started, :password, :password_confirmation, :language,
attr_accessible :getting_started, :password, :password_confirmation, :language, :grammatical_gender
def strip_and_downcase_username
if username.present?

View file

@ -3,7 +3,7 @@
%h4
= t('.invite_someone_to_join')
%i
= t('.if_they_accept_info')
= t('.if_they_accept_info', :gender => current_user)
%br
= t('.comma_seperated_plz')
= form_for User.new, :url => invitation_path(User) do |invite|

View file

@ -47,7 +47,7 @@
- else
.floating
%h3
= t('.not_connected', :name => @person.name)
= t('.not_connected', :name => @person.name, :gender => current_user)
- unless pending_request_for(@person)
%h3
@ -59,5 +59,5 @@
- else
%h3
.description
= t('.already_requested', :name => @person.name)
= t('.already_requested', :name => @person.name, :gender => current_user)

View file

@ -33,7 +33,7 @@
= p.text_field :caption, :value => @photo.caption
= p.submit t('.update_photo')
%p
= button_to t('.delete_photo'), @photo, :confirm => t('are_you_sure'), :method => :delete
= button_to t('.delete_photo'), @photo, :confirm => t('are_you_sure', :gender => current_user), :method => :delete
.span-9.last
- if @photo.status_message_id

View file

@ -24,5 +24,5 @@
= f.submit t('.update')
%h3 t('.cancel_my_account')
%p
= t('.unhappy') #{link_to t('.cancel_my_account'), registration_path(resource_name), :confirm => t('are_you_sure'), :method => :delete}.
= t('.unhappy') #{link_to t('.cancel_my_account'), registration_path(resource_name), :confirm => t('are_you_sure', :gender=>:neuter), :method => :delete}.
= link_to t('back'), :back

View file

@ -21,7 +21,7 @@
%ul#request_result{:aspect_id => aspect_id}
%li.error.hidden
#message
= link_to t('.know_email'), "#invite_user_pane", :class => "invite_user_button"
= link_to t('.know_email', :gender => current_user), "#invite_user_pane", :class => "invite_user_button"
%br
.yo{ :style => "display:none;"}
#invite_user_pane

View file

@ -23,7 +23,7 @@
- reshare_aspects = aspects_without_post(aspects, post)
- unless reshare_aspects.empty?
= render 'shared/reshare', :aspects => reshare_aspects, :post => post
= link_to t('delete'), status_message_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete"
= link_to t('delete'), status_message_path(post), :confirm => t('are_you_sure', :gender => current_user), :method => :delete, :remote => true, :class => "delete"
= render 'status_messages/status_message', :post => post, :photos => photos

View file

@ -23,6 +23,6 @@
- if current_user.owns?(post)
.right
= link_to t('delete'), photo_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete"
= link_to t('delete'), photo_path(post), :confirm => t('are_you_sure', :gender => current_user), :method => :delete, :remote => true, :class => "delete"
/= render "comments/comments", :post => post

View file

@ -16,7 +16,7 @@
.time
= how_long_ago(@status_message)
- if current_user.owns? @status_message
= link_to t('.destroy'), @status_message, :confirm => t('are_you_sure'), :method => :delete
= link_to t('.destroy'), @status_message, :confirm => t('are_you_sure', :gender => current_user), :method => :delete
.span-9.last
%h4{:style=>"margin-bottom:5px;"}= t('_comments')

View file

@ -3,6 +3,10 @@
-# the COPYRIGHT file.
:javascript
var genderized_languages = #{grammatical_gender_languages}
$(document).ready(function(){
$("#settings_nav li > a").live("click", function() {
var target = "#"+$(this).attr('class');
if( !$(target).is(":visible") ) {
@ -12,6 +16,18 @@
}
});
$("#user_language").change(function () {
var gselected = $("#user_language option:selected").val();
if ( $.inArray(gselected, genderized_languages) < 0 ) {
$("#grammatical_gender_block").hide(800);
$("#user_grammatical_gender").attr("disabled", "disabled");
} else {
$("#user_grammatical_gender").removeAttr("disabled");
$("#grammatical_gender_block").show(800);
}
});
});
#section_header
%h2
= t('settings')
@ -67,6 +83,9 @@
%p
= f.select :language, available_language_options
%span#grammatical_gender_block{options_for_grammatical_gender_block}
= " " + t('.address_me_as')
= select_tag 'user[grammatical_gender]', options_for_gender_select, :disabled => gender_select_disabled
= f.submit t('.change_language')
%br
@ -83,5 +102,5 @@
%h3
= t('.close_account')
= link_to t('.close_account'), current_user,
:confirm => t('are_you_sure'), :method => :delete,
:confirm => t('are_you_sure', :gender => current_user), :method => :delete,
:class => "button"

View file

@ -12,7 +12,7 @@
$(".aspects li").find(".delete").live("click", function(){
var aspectElement = $(this).parent("li");
if (confirm("#{t('are_you_sure')}")){
if (confirm("#{t('are_you_sure', :gender => current_user)}")){
aspectElement.fadeOut(300, function(){aspectElement.remove();});
}
});

View file

@ -1,3 +1,5 @@
# Encoding: utf-8
#
# Copyright (c) 2010, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
@ -14,3 +16,90 @@ AVAILABLE_LANGUAGE_CODES.each do |c|
I18n.fallbacks[c.to_sym] = [c.to_sym, DEFAULT_LANGUAGE.to_sym, :en]
end
end
# Languages that require to specify biological gender (sex)
# in order to inflect properly when using nouns with second
# person in past tense.
module I18n
module Backend
module Genderize
# Languages that need and support inflection.
SupportedLanguages = [ :ar, :lt, :pl, :ru, :sr, :uk ]
# Genders table helps to initialize grammatical gender
# by looking at sociological gender entered by user.
Genders = {
:feminine => %w( f fem female feminine k kobieta pani woman
laska girl dziewczyna dziewucha chick lady mrs mrs.
miss missus missis mistress ms panna panienka ۃ
dziewczynka żona zena sayyidah Пані Госпожа Г-жа ),
:masculine => %w( m mal male masculine man dude guy gentleman
mr mister pan chłopak boy chłopiec koleś gość
lasek gostek monsieur hr herr Пан mr. سيد سادة
mężczyzna mąż chłopaczek facet sayyid Господин Г-н maleman ),
}
Genders.default = :neuter
end
end
end
# Grammatical gender aware translate.
module I18n
module Backend
module Genderize
def translate(locale, key, options = {})
subkey = options.delete(:gender)
if not (subkey.nil? || key.is_a?(Enumerable))
subkey = subkey.grammatical_gender if subkey.respond_to?(:grammatical_gender)
subkey = Genders.default unless Genders.has_key?(subkey.to_sym)
key = "#{key}.#{subkey}".to_sym
end
super(locale, key, options)
end
# Initialize fast mapping table using data from Genders.
def included(m)
return if instance_variable_defined?(:@genders_guesser)
@genders_guesser = {}
@known_genders = []
Genders.each_pair do |gname,gtable|
@known_genders.push gname
gtable.each do |word|
@genders_guesser[word.to_sym] = gname
end
end
@genders_guesser.default = Genders.default
@known_genders.push Genders.default
@known_genders.map! { |g| g.to_s }
nil
end
module_function :included
# Does language needs and supports inflection by gender?
def supports?(l=nil)
SupportedLanguages.include? l.nil? ? I18n.locale.to_sym : l.to_sym
end
module_function :supports?
# Deduce grammatical gender using given gender and mapping.
def guess(gender_description="")
@genders_guesser[gender_description.downcase.to_sym]
end
module_function :guess
# Array of strings with known grammatical genders.
def known_genders
@known_genders
end
module_function :known_genders
end
end
end
I18n::Backend::Simple.send(:include, I18n::Backend::Genderize)

View file

@ -21,7 +21,10 @@ en:
email: "Email"
password: "Password"
password_confirmation: "Password confirmation"
are_you_sure: "Are you sure?"
are_you_sure:
masculine: "Are you sure?"
feminine: "Are you sure?"
neuter: "Are you sure?"
fill_me_out: "Fill me out"
back: "Back"
the_world: "the world"
@ -103,7 +106,10 @@ en:
your_diaspora_username_is: "Your Diaspora username is: %{diaspora_handle}"
create_request: "Find by Diaspora handle"
diaspora_handle: "diaspora@handle.org"
know_email: "Know their email address? You should invite them"
know_email:
masculine: "Know their email address? You should invite them"
feminine: "Know their email address? You should invite them"
neuter: "Know their email address? You should invite them"
invitations:
invite_someone: "Invite someone"
invitations_left: "(%{count} left)"
@ -159,7 +165,10 @@ en:
helper:
remove: "remove"
aspect_not_empty: "Aspect not empty"
are_you_sure: "Are you sure you want to delete this aspect?"
are_you_sure:
masculine: "Are you sure you want to delete this aspect?"
feminine: "Are you sure you want to delete this aspect?"
neuter: "Are you sure you want to delete this aspect?"
remove_from_aspect:
success: "Successfully removed person from aspect"
failure: "Failed to remove person from aspect"
@ -183,6 +192,10 @@ en:
download_photos: "download my photos"
your_handle: "Your diaspora handle"
your_email: "Your email"
address_me_as: "Address me as"
masculine: "a man"
feminine: "a woman"
neuter: "a neuter"
destroy: "Account successfully closed."
getting_started:
welcome: "Welcome to Diaspora!"
@ -241,9 +254,18 @@ en:
back_to_list: "Back to List"
post_it: "post it!"
create:
runtime_error: "Photo upload failed. Are you sure that your seatbelt is fastened?"
integrity_error: "Photo upload failed. Are you sure that was an image?"
type_error: "Photo upload failed. Are you sure an image was added?"
runtime_error:
masculine: "Photo upload failed. Are you sure that your seatbelt is fastened?"
feminine: "Photo upload failed. Are you sure that your seatbelt is fastened?"
neuter: "Photo upload failed. Are you sure that your seatbelt is fastened?"
integrity_error:
masculine: "Photo upload failed. Are you sure that was an image?"
feminine: "Photo upload failed. Are you sure that was an image?"
neuter: "Photo upload failed. Are you sure that was an image?"
type_error:
masculine: "Photo upload failed. Are you sure an image was added?"
feminine: "Photo upload failed. Are you sure an image was added?"
neuter: "Photo upload failed. Are you sure an image was added?"
update:
notice: "Photo successfully updated."
error: "Failed to edit photo."
@ -272,11 +294,20 @@ en:
sent: "Invitations have been sent to: "
rejected: "The following email addresses had problems: "
no_more: "You have no more invitations."
already_sent: "You already invited this person."
already_contacts: "You are already connected with this person"
already_sent:
masculine: "You already invited this person."
feminine: "You already invited this person."
neuter: "You already invited this person."
already_contacts:
masculine: "You are already connected with this person."
feminine: "You are already connected with this person."
neuter: "You are already connected with this person."
new:
invite_someone_to_join: "Invite someone to join Diaspora!"
if_they_accept_info: "if they accept, they will be added to the aspect you invited them."
if_they_accept_info:
masculine: "if they accept, they will be added to the aspect you invited them."
feminine: "if they accept, they will be added to the aspect you invited them."
neuter: "if they accept, they will be added to the aspect you invited them."
comma_seperated_plz: "You can enter multiple email addresses separated by commas."
to: "To"
message: "Message:"
@ -307,10 +338,16 @@ en:
incoming_request: "You have an incoming request from this person."
return_to_aspects: "Return to your aspects page"
to_accept_or_ignore: "to accept or ignore it."
request_people: "If you'd like, you can request to place him/her in one of your aspects."
already_requested: "You have already sent a request to %{name}."
request_people: "If you'd like, you can request to place him in one of your aspects."
already_requested:
masculine: "You have already sent a request to %{name}."
feminine: "You have already sent a request to %{name}."
neuter: "You have already sent a request to %{name}."
does_not_exist: "Person does not exist!"
not_connected: "You are not connected with this person"
not_connected:
masculine: "You are not connected with this person"
feminine: "You are not connected with this person"
neuter: "You are not connected with this person"
edit:
info_available_to: "This info will be available to whomever you connect with on Diaspora."
your_profile: "Your profile"
@ -350,7 +387,10 @@ en:
ignore: "Ignored contact request."
create:
sending: "Sending"
sent: "You've asked to share with %{name}. They should see it next time they log in to Diaspora."
sent:
masculine: "You've asked to share with %{name}. They should see it next time they log in to Diaspora."
feminine: "You've asked to share with %{name}. They should see it next time they log in to Diaspora."
neuter: "You've asked to share with %{name}. They should see it next time they log in to Diaspora."
new_request_to_person:
sent: "sent!"
services:

View file

@ -11,6 +11,7 @@ describe UsersController do
let!(:old_password) { user.encrypted_password }
let!(:old_language) { user.language }
let!(:old_gender) { user.grammatical_gender }
before do
sign_in :user, user
@ -59,5 +60,28 @@ describe UsersController do
user.language.should_not == old_language
end
end
describe 'grammatical_gender' do
it 'should allow user to change his grammatical gender for some languages' do
user.language = 'pl'
user.grammatical_gender = 'masculine'
user.save
old_gender = user.grammatical_gender
put("update", :id => user.id, "user" => {"language" => "ru", "grammatical_gender" => "neuter"})
user.reload
user.grammatical_gender.should_not == old_gender
old_gender = user.grammatical_gender
put("update", :id => user.id, "user" => {"language" => "ru", "grammatical_gender" => ""})
user.reload
user.grammatical_gender.should == old_gender
put("update", :id => user.id, "user" => {"language" => "ru", "grammatical_gender" => "feminine"})
user.reload
old_gender = user.grammatical_gender
put("update", :id => user.id, "user" => {"language" => "en"})
user.reload
user.grammatical_gender.should == old_gender
end
end
end
end

View file

@ -167,6 +167,22 @@ describe User do
end
end
describe "of grammatical gender" do
after do
I18n.locale = :en
end
it "requires availability" do
user = Factory.build(:user, :grammatical_gender => 'some invalid string')
user.should_not be_valid
end
it "should save with empty grammatical gender if blank" do
I18n.locale = :pl
user = Factory(:user, :grammatical_gender => nil)
user.grammatical_gender.should == nil
end
end
end
describe ".build" do