From 63871baf6061668b162972193c55b5a8f7490797 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 30 Apr 2015 00:32:33 -0700 Subject: added support for email notifications of ticket changes --- Gemfile | 13 ++- Gemfile.lock | 4 + app/controllers/controller_extension/flash.rb | 18 +++- app/controllers/users_controller.rb | 5 +- app/mailers/.gitkeep | 0 app/models/anonymous_user.rb | 4 + app/models/user.rb | 10 ++ app/views/users/_contact_email.html.haml | 9 ++ config/defaults.yml | 24 ++++- config/locales/users.en.yml | 5 + engines/support/Gemfile | 6 -- engines/support/Gemfile.lock | 14 +++ engines/support/Rakefile | 12 +-- .../support/app/controllers/tickets_controller.rb | 8 ++ engines/support/app/helpers/ticket_i18n_helper.rb | 20 ++++ engines/support/app/mailers/.gitkeep | 0 engines/support/app/mailers/ticket_mailer.rb | 82 ++++++++++++++++ engines/support/app/models/ticket.rb | 43 ++++++--- engines/support/app/models/ticket_comment.rb | 40 +++----- .../app/views/ticket_mailer/notice.text.erb | 5 + engines/support/config/locales/en.yml | 8 +- engines/support/config/locales/es.yml | 3 + engines/support/leap_web_help.gemspec | 6 +- engines/support/test/factories.rb | 2 +- .../support/test/functional/ticket_mailer_test.rb | 7 ++ engines/support/test/unit/ticket_test.rb | 2 +- lib/en_US.yml | 106 ++++++++++++++++++++- lib/tasks/i18n.rake | 44 +++++---- 28 files changed, 404 insertions(+), 96 deletions(-) delete mode 100644 app/mailers/.gitkeep create mode 100644 app/views/users/_contact_email.html.haml create mode 100644 engines/support/Gemfile.lock create mode 100644 engines/support/app/helpers/ticket_i18n_helper.rb create mode 100644 engines/support/app/mailers/.gitkeep create mode 100644 engines/support/app/mailers/ticket_mailer.rb create mode 100644 engines/support/app/views/ticket_mailer/notice.text.erb create mode 100644 engines/support/config/locales/es.yml create mode 100644 engines/support/test/functional/ticket_mailer_test.rb diff --git a/Gemfile b/Gemfile index a545dd6..419b870 100644 --- a/Gemfile +++ b/Gemfile @@ -48,10 +48,6 @@ group :production do # for list of supported runtimes. end -## MISC -gem 'certificate_authority', # unreleased so far ... but leap_web_certs need it - :git => 'https://github.com/cchandler/certificate_authority.git' - ## ## ENVIRONMENT SPECIFIC GEMS ## @@ -102,3 +98,12 @@ end custom_gems.each do |name, gem_info| gem gem_info[:name], :path => gem_info[:path] end + +## +## DEPENDENCIES FOR OPTIONAL ENGINES +## + +gem 'certificate_authority', # unreleased so far ... but leap_web_certs need it + :git => 'https://github.com/cchandler/certificate_authority.git' + +gem 'valid_email' # used by leap_web_help diff --git a/Gemfile.lock b/Gemfile.lock index 4de7e05..e140303 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -235,6 +235,9 @@ GEM uglifier (1.2.7) execjs (>= 0.3.0) multi_json (~> 1.3) + valid_email (0.0.7) + activemodel + mail warden (1.2.3) rack (>= 1.0) websocket-driver (0.5.1) @@ -284,3 +287,4 @@ DEPENDENCIES therubyracer (~> 0.10.2) thin uglifier (~> 1.2.7) + valid_email diff --git a/app/controllers/controller_extension/flash.rb b/app/controllers/controller_extension/flash.rb index 1642141..45072cf 100644 --- a/app/controllers/controller_extension/flash.rb +++ b/app/controllers/controller_extension/flash.rb @@ -4,9 +4,13 @@ module ControllerExtension::Flash protected def flash_for(resource, options = {}) - return unless resource.changed? - add_flash_message_for resource - add_flash_errors_for resource if options[:with_errors] + if resource.is_a? Exception + add_flash_message_for_exception resource + else + return unless resource.changed? + add_flash_message_for resource + add_flash_errors_for resource if options[:with_errors] + end end def add_flash_message_for(resource) @@ -40,4 +44,12 @@ module ControllerExtension::Flash flash[:error] += "
" flash[:error] += resource.errors.full_messages.join(".
") end + + # + # This is pretty crude. It would be good to l10n in the future. + # + def add_flash_message_for_exception(exc) + flash[:error] = exc.to_s + end + end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index dcf7607..3943afc 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -40,7 +40,10 @@ class UsersController < ApplicationController ## added so updating service level works, but not sure we will actually want this. also not sure that this is place to prevent user from updating own effective service level, but here as placeholder: def update @user.update_attributes(params[:user]) unless (!admin? and params[:user][:effective_service_level]) - respond_with @user + if @user.valid? + flash[:notice] = I18n.t(:changes_saved) + end + respond_with @user, :location => edit_user_path(@user) end def deactivate diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/anonymous_user.rb b/app/models/anonymous_user.rb index 87239eb..0c1f540 100644 --- a/app/models/anonymous_user.rb +++ b/app/models/anonymous_user.rb @@ -28,4 +28,8 @@ class AnonymousUser < Object def messages [] end + + def is_anonymous? + true + end end diff --git a/app/models/user.rb b/app/models/user.rb index 52e20dd..d44df40 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,6 +6,8 @@ class User < CouchRest::Model::Base property :login, String, :accessible => true property :password_verifier, String, :accessible => true property :password_salt, String, :accessible => true + property :contact_email, String, :accessible => true + property :contact_email_key, String, :accessible => true property :enabled, TrueClass, :default => true @@ -33,6 +35,10 @@ class User < CouchRest::Model::Base :confirmation => true, :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" } + validates :contact_email, :allow_blank => true, + :email => true, + :mx_with_fallback => true + timestamps! design do @@ -90,6 +96,10 @@ class User < CouchRest::Model::Base APP_CONFIG['admins'].include? self.login end + def is_anonymous? + false + end + def most_recent_tickets(count=3) Ticket.for_user(self).limit(count).all #defaults to having most recent updated first end diff --git a/app/views/users/_contact_email.html.haml b/app/views/users/_contact_email.html.haml new file mode 100644 index 0000000..ad768b7 --- /dev/null +++ b/app/views/users/_contact_email.html.haml @@ -0,0 +1,9 @@ +%legend= t(:contact_email) += simple_form_for @user, {:validate => true} do |f| + %p= t(:contact_email_info) + = f.input :contact_email, :label => false + -# %p= t(:public_key) + -# = f.text_area :contact_email_key, {:class => "full-width", :rows => 4} + .control-group + .controls + = f.submit t(:save), :class => 'btn', :data => {"loading-text" => "Saving..."} \ No newline at end of file diff --git a/config/defaults.yml b/config/defaults.yml index b14955c..ca5107f 100644 --- a/config/defaults.yml +++ b/config/defaults.yml @@ -34,13 +34,29 @@ common: &common token_expires_after: 60 # handles that will be blocked from being used as logins or email aliases # in addition to the ones in /etc/passwd and http://tools.ietf.org/html/rfc2142 - handle_blacklist: ['certmaster', 'ssladmin', 'arin-admin', 'administrator', 'www-data', 'maildrop', 'postmaster', 'admin', 'contact', 'info'] + handle_blacklist: + - certmaster + - ssladmin + - arin-admin + - administrator + - www-data + - maildrop + - postmaster + - admin + - contact + - info + - noreply + - robot + - helpdesk + - help + - tickets + - owner # handles that will be allowed despite being in /etc/passwd or rfc2142 handle_whitelist: [] # actions enabled in the account settings # see /users/app/views/users/_edit.html.haml for a list. - user_actions: ['destroy_account'] - admin_actions: ['change_pgp_key', 'change_service_level', 'destroy_account'] + user_actions: ['contact_email', 'destroy_account'] + admin_actions: ['contact_email', 'change_pgp_key', 'change_service_level', 'destroy_account'] billing: ~ default_locale: :en available_locales: @@ -54,6 +70,8 @@ common: &common soledad-service: 'public/1/config/soledad-service.json' eip-service: 'public/1/config/eip-service.json' smtp-service: 'public/1/config/smtp-service.json' + mailer: + from_address: 'noreply' service_levels: &service_levels service_levels: diff --git a/config/locales/users.en.yml b/config/locales/users.en.yml index bebf1cf..89307dd 100644 --- a/config/locales/users.en.yml +++ b/config/locales/users.en.yml @@ -39,6 +39,11 @@ en: payment_one_month_warning: "We hope you have been enjoying this service this past month. Please sign up to pay within the next month, by %{date_in_one_month}. Directions for payment are available at INSERT_URL" bye: "Goodbye!" bye_message: "So long and thanks for all the fish." + contact_email: "Contact Email" + contact_email_info: > + You may specify an email address where we can contact you if there is a + problem with your account. For privacy reasons, you might prefer to leave + this empty. # # overview diff --git a/engines/support/Gemfile b/engines/support/Gemfile index ad7d29b..0296338 100644 --- a/engines/support/Gemfile +++ b/engines/support/Gemfile @@ -1,11 +1,5 @@ source "https://rubygems.org" -eval(File.read(File.dirname(__FILE__) + '/../common_dependencies.rb')) -eval(File.read(File.dirname(__FILE__) + '/..//ui_dependencies.rb')) - -# We require leap_web_core from here so we can use the path option. -gem "leap_web_core", :path => '../core' - # Declare your gem's dependencies in leap_web_users.gemspec. # Bundler will treat runtime dependencies like base dependencies, and # development dependencies will be added by default to the :development group. diff --git a/engines/support/Gemfile.lock b/engines/support/Gemfile.lock new file mode 100644 index 0000000..fe4ce6c --- /dev/null +++ b/engines/support/Gemfile.lock @@ -0,0 +1,14 @@ +PATH + remote: . + specs: + leap_web_help (0.6.0) + +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + ruby + +DEPENDENCIES + leap_web_help! diff --git a/engines/support/Rakefile b/engines/support/Rakefile index 0e73163..4787e78 100644 --- a/engines/support/Rakefile +++ b/engines/support/Rakefile @@ -1,8 +1,4 @@ #!/usr/bin/env rake - -require 'rake/packagetask' -require 'rubygems/package_task' - begin require 'bundler/setup' rescue LoadError @@ -18,16 +14,14 @@ end RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'LeapWebHelp' + rdoc.title = 'Blah' rdoc.options << '--line-numbers' rdoc.rdoc_files.include('README.rdoc') rdoc.rdoc_files.include('lib/**/*.rb') end -spec = eval(File.read('leap_web_help.gemspec')) -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec -end + + Bundler::GemHelper.install_tasks diff --git a/engines/support/app/controllers/tickets_controller.rb b/engines/support/app/controllers/tickets_controller.rb index 602bbd9..c1abfa2 100644 --- a/engines/support/app/controllers/tickets_controller.rb +++ b/engines/support/app/controllers/tickets_controller.rb @@ -62,6 +62,7 @@ class TicketsController < ApplicationController if @ticket.comments_changed? @ticket.comments.last.posted_by = current_user.id @ticket.comments.last.private = false unless admin? + send_email_update(@ticket, @ticket.comments.last) end flash_for @ticket, with_errors: true @@ -153,4 +154,11 @@ class TicketsController < ApplicationController ) end + def send_email_update(ticket, comment) + TicketMailer.send_notice(ticket, comment, ticket_url(ticket)) + rescue StandardError => exc + flash_for(exc) + raise exc if Rails.env == 'development' + end + end diff --git a/engines/support/app/helpers/ticket_i18n_helper.rb b/engines/support/app/helpers/ticket_i18n_helper.rb new file mode 100644 index 0000000..61b4cf2 --- /dev/null +++ b/engines/support/app/helpers/ticket_i18n_helper.rb @@ -0,0 +1,20 @@ +module TicketI18nHelper + + # + # outputs translations for all the possible translated strings. + # used in emails, sense we don't know the locale of the recipient. + # + def t_all_available(key) + default = I18n.t(key, locale: I18n.default_locale) + result = [] + result << "[#{I18n.default_locale}] #{default}" + I18n.available_locales.each do |locale| + text = I18n.t(key, locale: locale, default: default) + if text != default + result << "[#{locale}] #{text}" + end + end + result.join("\n") + end + +end diff --git a/engines/support/app/mailers/.gitkeep b/engines/support/app/mailers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/engines/support/app/mailers/ticket_mailer.rb b/engines/support/app/mailers/ticket_mailer.rb new file mode 100644 index 0000000..9a83345 --- /dev/null +++ b/engines/support/app/mailers/ticket_mailer.rb @@ -0,0 +1,82 @@ +class TicketMailer < ActionMailer::Base + + helper :ticket_i18n + + def self.send_notice(ticket, comment, url) + reply_recipients(ticket, comment.user).each do |email, key| + TicketMailer.notice(ticket, comment, url, email, key).deliver + end + end + + # + # ticket - the ticket in question + # author - user who created last comment + # url - url of the ticket + # email - email to send reply notice to + # key - public key for email + # + # TODO: OpenPGP encrypt email to the public key. + # + def notice(ticket, comment, url, email, key) + @url = url + @email = email + @key = key + mail({ + :reply_to => reply_to_address, + :subject => "Re: [#{ticket.id}] #{ticket.subject}", + :to => email, + :from => from_address(ticket, comment) + }) + end + + private + + # + # I am not sure what makes the most sense here. For now, since we do not + # include any reply text in the notification email, it makes sense to make + # the from address be the robot, not the user. + # + def from_address(ticket, comment) + if true + reply_to_address + else + from_name = if comment.user.present? + comment.user.login + elsif ticket.created_by.present? + ticket.created_by_user.login + else + I18n.t(:anonymous) + end + "%s <%s>" % [from_name, reply_to_address] + end + end + + # TODO: change me to support virtual domains + def reply_to_address + [APP_CONFIG[:mailer][:from_address], APP_CONFIG[:domain]].join('@') + end + + # + # returns a hash of {'email' => 'public key'}, where key might be nil. + # + def self.reply_recipients(ticket, author) + recipients = {} + ticket.comments.each do |comment| + user = comment.posted_by_user + if user && (author.nil? || user.id != author.id) + if user.email + recipients[user.identity.address] = (user.identity.keys[:pgp] if user.identity.keys[:pgp].present?) + end + if user.contact_email.present? + recipients[user.contact_email] = (user.contact_email_key if user.contact_email_key.present?) + end + end + end + if author && author.is_admin? && ticket.email.present? + recipients[ticket.email] = nil + end + logger.info { "emailing reply regarding ticket #{ticket.id} to #{recipients.keys.join(', ')}" } + recipients + end + +end diff --git a/engines/support/app/models/ticket.rb b/engines/support/app/models/ticket.rb index 554fbd6..b1bdf8d 100644 --- a/engines/support/app/models/ticket.rb +++ b/engines/support/app/models/ticket.rb @@ -3,10 +3,10 @@ # look into whether that should be tweaked, and whether it works okay with # pagination (seems to now...) # -# TODO: better validation of email -# # TODO: don't hardcode strings 'unknown user' and 'unauthenticated user' # +# TODO: this should use associations instead of non-standard created_by. +# class Ticket < CouchRest::Model::Base use_database "tickets" @@ -19,6 +19,8 @@ class Ticket < CouchRest::Model::Base timestamps! + unique_id :generate_code + design do view :by_updated_at view :by_created_at @@ -33,13 +35,11 @@ class Ticket < CouchRest::Model::Base validates :subject, :presence => true - # email can have three states: - # * nil - prefilled with created_by's email - # * "" - cleared - # * valid email address - validates :email, :allow_blank => true, :format => /\A(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?\Z/ - - # validates :comments, presence: true + # email can be nil, "", or valid address. + # validation provided by 'valid_email' gem. + validates :email, :allow_blank => true, + :email => true, + :mx_with_fallback => true def self.search(options = {}) @selection = TicketSelection.new(options) @@ -74,17 +74,21 @@ class Ticket < CouchRest::Model::Base self.is_open = true end - def commenters + def commenters(locale = I18n.locale) commenters = [] + unknown = false + anonymous = false self.comments.each do |comment| if comment.posted_by if user = User.find(comment.posted_by) commenters << user.login if user and !commenters.include?(user.login) - else - commenters << 'unknown user' if !commenters.include?('unknown user') + elsif !unknown + unknown = true + commenters << I18n.t(:unknown, :locale => locale) end - else - commenters << 'unauthenticated user' if !commenters.include?('unauthenticated user') + elsif !anonymous + anonymous = true + commenters << I18n.t(:anonymous, :locale => locale) end end commenters.join(', ') @@ -113,4 +117,15 @@ class Ticket < CouchRest::Model::Base User.find_by_login(self.regarding_user) end + # Generate a unique code for identifying this ticket. + # This will become the ID of the document. It is also used for + # tracking email replies. The code must be URL friendly. + def generate_code + while code = SecureRandom.urlsafe_base64.downcase.gsub(/[li1o0_-]/,'')[0..7] + if code.length == 8 && self.class.find(code).nil? + return code + end + end + end + end diff --git a/engines/support/app/models/ticket_comment.rb b/engines/support/app/models/ticket_comment.rb index 2c5df41..1c04a1f 100644 --- a/engines/support/app/models/ticket_comment.rb +++ b/engines/support/app/models/ticket_comment.rb @@ -1,22 +1,17 @@ +# +# elijah: the property 'posted_by' should have been a belongs_to() association +# or at least 'posted_by_id', but I am leaving it as is because I am +# not sure how to change it. +# class TicketComment include CouchRest::Model::Embeddable - #belongs_to :ticket #is this best way to do it? will want to access all of a tickets comments, so maybe this isn't the way? - property :posted_by, String#, :protected => true #Integer#this should be current_user if that is set, meaning the user is logged in #cannot have it be protected and set via comments_attributes=. also, if it is protected and we set in the tickets_controller, it gets unset. TODO---is this okay to have it not protected and manually check it? We do not users to be able to set this. - # if the current user is not set, then we could just say the comment comes from an 'unauthenticated user', which would be somebody with the secret URL - property :posted_at, Time#, :protected => true - #property :posted_verified, TrueClass, :protected => true #should be true if current_user is set when the comment is created + property :posted_by, String + property :posted_at, Time property :body, String - property :private, TrueClass # private comments are only viewable by admins #this is checked when set, to make sure it was set by an admin + property :private, TrueClass - # ? timestamps! validates :body, :presence => true - #before_validation :set_time#, :set_posted_by - - #design do - # view :by_posted_at - # view :by_body - #end # translations are in the same scope as those of a "proper" couchrest model def self.i18n_scope @@ -28,21 +23,10 @@ class TicketComment end def posted_by_user - User.find(posted_by) if posted_by - end - -=begin - #TODO. - #this is resetting all comments associated with the ticket: - def set_time - self.posted_at = Time.now - end -=end - -=begin - def set_posted_by - self.posted_by = User.current if User.current + if posted_by + @_posted_by_user ||= User.find(posted_by) + end end -=end + alias user posted_by_user end diff --git a/engines/support/app/views/ticket_mailer/notice.text.erb b/engines/support/app/views/ticket_mailer/notice.text.erb new file mode 100644 index 0000000..c0e6f44 --- /dev/null +++ b/engines/support/app/views/ticket_mailer/notice.text.erb @@ -0,0 +1,5 @@ +<%= t_all_available(:email_notice_text) %> + + <%= @url %> + +<%= t_all_available(:email_no_reply_text) %> \ No newline at end of file diff --git a/engines/support/config/locales/en.yml b/engines/support/config/locales/en.yml index 8d2af67..071db97 100644 --- a/engines/support/config/locales/en.yml +++ b/engines/support/config/locales/en.yml @@ -1,9 +1,11 @@ en: support_tickets: "Support" + email_notice_text: "A new comment has been added to this help ticket." + email_no_reply_text: "Do not reply to this email." # translations used in the layout views or @title layouts: # fallback for all translations of "tickets" nested below: - tickets: "Tickets" + tickets: "Tickets" title: tickets: "Tickets" header: @@ -90,8 +92,8 @@ en: "false": "Closed" # mouse over hints for the given fields hints: - ticket: - email: "Please provide an email address so we can get back to you." + ticket: + email: "Provide an email address in order to notified when this ticket is updated." # these will fallback to translations in "simple_form.hints.defaults" # placeholders inside the fields before anything was typed #placeholders: diff --git a/engines/support/config/locales/es.yml b/engines/support/config/locales/es.yml new file mode 100644 index 0000000..7773197 --- /dev/null +++ b/engines/support/config/locales/es.yml @@ -0,0 +1,3 @@ +es: + email_notice_text: "Se ha aƱadido un nuevo comentario." + email_no_reply_text: "No responda a este mensaje." \ No newline at end of file diff --git a/engines/support/leap_web_help.gemspec b/engines/support/leap_web_help.gemspec index 7b668d5..818604f 100644 --- a/engines/support/leap_web_help.gemspec +++ b/engines/support/leap_web_help.gemspec @@ -8,9 +8,9 @@ Gem::Specification.new do |s| s.version = LeapWeb::VERSION s.authors = ["Jessib"] s.email = ["jessib@leap.se"] - s.homepage = "http://www.leap.se" - s.summary = "Help Desk for LeapWeb" - s.description = "Managing Tickets for a Leap provider" + s.homepage = "https://www.leap.se" + s.summary = "Help Desk for LEAP webapp" + s.description = "Managing help tickets for a LEAP provider" s.files = Dir["{app,config,lib}/**/*"] + ["Rakefile", "README.md"] s.test_files = Dir["test/**/*"] diff --git a/engines/support/test/factories.rb b/engines/support/test/factories.rb index bcf41e8..3fc28b3 100644 --- a/engines/support/test/factories.rb +++ b/engines/support/test/factories.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :ticket do subject { Faker::Lorem.sentence } - email { Faker::Internet.email } + email { "fake@example.org" } factory :ticket_with_comment do comments_attributes do diff --git a/engines/support/test/functional/ticket_mailer_test.rb b/engines/support/test/functional/ticket_mailer_test.rb new file mode 100644 index 0000000..6bbaaaf --- /dev/null +++ b/engines/support/test/functional/ticket_mailer_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class TicketMailerTest < ActionMailer::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/engines/support/test/unit/ticket_test.rb b/engines/support/test/unit/ticket_test.rb index 678d8dc..c64e8f4 100644 --- a/engines/support/test/unit/ticket_test.rb +++ b/engines/support/test/unit/ticket_test.rb @@ -13,7 +13,7 @@ class TicketTest < ActiveSupport::TestCase end test "ticket validates email format" do - t = FactoryGirl.build :ticket, email: "aswerssfd" + t = FactoryGirl.build :ticket, email: "invalid email" assert !t.valid? end diff --git a/lib/en_US.yml b/lib/en_US.yml index b00f29e..5c4190f 100644 --- a/lib/en_US.yml +++ b/lib/en_US.yml @@ -1,6 +1,6 @@ en_US: -### Do NOT edit this file directly, as all changes will be overwritten by `rake i18:bundle` +### Do NOT edit this file directly, as all changes will be overwritten by `rake i18n:bundle` ### Instead, make changes in the appropriate file in config/locales. ### Source strings in transifex are automatically updated from this file (via github url) @@ -157,6 +157,12 @@ en_US: for payment are available at INSERT_URL bye: Goodbye! bye_message: So long and thanks for all the fish. + contact_email: Contact Email + contact_email_info: ! 'You may specify an email address where we can contact you if + there is a problem with your account. For privacy reasons, you might prefer to leave + this empty. + + ' users: overview: welcome: Welcome %{username}. @@ -183,3 +189,101 @@ en_US: placeholders: user: email_forward: my_other_email@domain.net + + +######################################## +### billing/config/locales/en.yml + create_new_customer: Create a new Braintree Customer + must_create_customer: You must store a customer in braintree before subscribing to + a plan + subscribe: Subscribe + save_customer_info: Save Customer Information + donation_not_payment: ! 'Note: This is a donation, and will not be applied towards + your account.' + no_relevant_subscription: No subscription which is Active, Pending, or Past Due + plan: Plan + description: Description + cost: Cost + free: Free + + +######################################## +### support/config/locales/en.yml + support_tickets: Support + email_notice_text: A new comment has been added to this help ticket. + email_no_reply_text: Do not reply to this email. + layouts: + tickets: Tickets + title: + tickets: Tickets + header: + tickets: Tickets + navigation: + tickets: Support Tickets + tickets: + all: All Tickets + open: Open Tickets + closed: Closed Tickets + new: New Ticket + created: Created at + updated: Updated at + subject: couchrest.models.tickets.attributes.subject + status: + open: Open + closed: Closed + action: + open: Open + close: Close + confirm: + destroy: + are_you_sure: Are you sure you want to destroy this ticket? + tabs: + all: All Tickets + open: Open Tickets + closed: Closed Tickets + index: + none: No tickets have been found. + voices: Voices + destroy: Destroy + post_reply: Post Reply + reply_and_close: Reply and Close + access_ticket_text: ! 'You can later access this ticket at the URL %{full_url}. + You might want to bookmark this page to find it again. Anybody with this URL will + be able to access this ticket, so if you are on a shared computer you might want + to remove it from the browser history. + + ' + helpers: + submit: + ticket: + create: Submit Ticket + update: Update Ticket + couchrest: + models: + ticket: Ticket + ticket_comment: Comment + attributes: + ticket: + subject: Subject + email: Email + regarding_user: Regarding User + regarding_account: Regarding Account + is_open: Status + ticket_comment: + body: Description + private: private + simple_form: + labels: + ticket: + regarding_: + email: Email + comments: + body: Description + options: + ticket: + is_open: + 'true': Open + 'false': Closed + hints: + ticket: + email: Provide an email address in order to notified when this ticket is updated. diff --git a/lib/tasks/i18n.rake b/lib/tasks/i18n.rake index daa1834..5ccc65e 100644 --- a/lib/tasks/i18n.rake +++ b/lib/tasks/i18n.rake @@ -1,6 +1,19 @@ require 'yaml' require 'fileutils' +def process_locale_file(output, file) + ## print separator to see where another file begins + output.write("\n\n" + '#' * 40 + "\n" + '### ' + file + "\n") + output.write( + # remove the toplevel "en" key + YAML.dump(YAML.load_file(file)['en']).lines.map { + |line| " #{line}" # << prefix all lines with two spaces (we're nested below "en:") + }[ + 1..-1 # << skip the first line (it's "---" and freaks out the parser if it sees it multiple times in a file) + ].join + ) +end + namespace :i18n do # # for coding, it helps to have the english strings in separate files. @@ -14,27 +27,20 @@ namespace :i18n do desc "combine config/locales/*.en.yml to lib/en_US.yml" task :bundle do en_yml = File.join(Rails.root, 'lib', 'en_US.yml') - Dir.chdir('config/locales/') do - if File.exists?(en_yml) - File.unlink en_yml - end - File.open(en_yml, 'w') do |output| - output.write("en_US:\n\n"+ - "### Do NOT edit this file directly, as all changes will be overwritten by `rake i18:bundle`\n"+ - "### Instead, make changes in the appropriate file in config/locales.\n"+ - "### Source strings in transifex are automatically updated from this file (via github url)") + File.open(en_yml, 'w') do |output| + output.write("en_US:\n\n"+ + "### Do NOT edit this file directly, as all changes will be overwritten by `rake i18n:bundle`\n"+ + "### Instead, make changes in the appropriate file in config/locales.\n"+ + "### Source strings in transifex are automatically updated from this file (via github url)") + Dir.chdir('config/locales/') do Dir.glob('*.en.yml').sort.each do |file| - ## print separator to see where another file begins - output.write("\n\n" + '#' * 40 + "\n" + '### ' + file + "\n") - output.write( - # remove the toplevel "en" key - YAML.load_file(file)['en']. - to_yaml. - # prefix all lines with two spaces (we're nested below "en:") - lines.map {|line| " #{line}"}[ - 1..-1 # << skip the first line (it's "---" and freaks out the parser if it sees it multiple times in a file) - ].join) + process_locale_file(output, file) + end + end + Dir.chdir('engines/') do + Dir.glob('*/config/locales/en.yml').sort.each do |file| + process_locale_file(output, file) end end puts "You can now find the bundled locale yml in: #{en_yml}" -- cgit v1.2.3