##// END OF EJS Templates
Basic plugin support....
Jean-Philippe Lang -
r741:e4f0864e3a7f
parent child
Show More
@@ -0,0 +1,8
1 <h2><%= l(:label_settings) %>: <%=h @plugin.name %></h2>
2
3 <% form_tag({:action => 'plugin'}) do %>
4 <div class="box tabular">
5 <%= render :partial => @partial, :locals => {:settings => @settings}%>
6 </div>
7 <%= submit_tag l(:button_apply) %>
8 <% end %>
@@ -0,0 +1,124
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine #:nodoc:
19
20 # Base class for Redmine plugins.
21 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
22 #
23 # Redmine::Plugin.register :example do
24 # name 'Example plugin'
25 # author 'John Smith'
26 # description 'This is an example plugin for Redmine'
27 # version '0.0.1'
28 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
29 # end
30 #
31 # === Plugin attributes
32 #
33 # +settings+ is an optional attribute that let the plugin be configurable.
34 # It must be a hash with the following keys:
35 # * <tt>:default</tt>: default value for the plugin settings
36 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
37 # Example:
38 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
39 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
40 #
41 # When rendered, the plugin settings value is available as the local variable +settings+
42 class Plugin
43 @registered_plugins = {}
44 class << self
45 attr_reader :registered_plugins
46 private :new
47
48 def def_field(*names)
49 class_eval do
50 names.each do |name|
51 define_method(name) do |*args|
52 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
53 end
54 end
55 end
56 end
57 end
58 def_field :name, :description, :author, :version, :settings
59
60 # Plugin constructor
61 def self.register(name, &block)
62 p = new
63 p.instance_eval(&block)
64 Plugin.registered_plugins[name] = p
65 end
66
67 # Adds an item to the given +menu+.
68 # The +id+ parameter (equals to the project id) is automatically added to the url.
69 # menu :project_menu, :label_plugin_example, :controller => 'example', :action => 'say_hello'
70 #
71 # Currently, only the project menu can be extended. Thus, the +name+ parameter must be +:project_menu+
72 def menu(name, label, url)
73 Redmine::MenuManager.map(name) {|menu| menu.push label, url}
74 end
75
76 # Defines a permission called +name+ for the given +actions+.
77 #
78 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
79 # permission :destroy_contacts, { :contacts => :destroy }
80 # permission :view_contacts, { :contacts => [:index, :show] }
81 #
82 # The +options+ argument can be used to make the permission public (implicitly given to any user)
83 # or to restrict users the permission can be given to.
84 #
85 # Examples
86 # # A permission that is implicitly given to any user
87 # # This permission won't appear on the Roles & Permissions setup screen
88 # permission :say_hello, { :example => :say_hello }, :public => true
89 #
90 # # A permission that can be given to any user
91 # permission :say_hello, { :example => :say_hello }
92 #
93 # # A permission that can be given to registered users only
94 # permission :say_hello, { :example => :say_hello }, :require => loggedin
95 #
96 # # A permission that can be given to project members only
97 # permission :say_hello, { :example => :say_hello }, :require => member
98 def permission(name, actions, options = {})
99 if @project_module
100 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
101 else
102 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
103 end
104 end
105
106 # Defines a project module, that can be enabled/disabled for each project.
107 # Permissions defined inside +block+ will be bind to the module.
108 #
109 # project_module :things do
110 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
111 # permission :destroy_contacts, { :contacts => :destroy }
112 # end
113 def project_module(name, &block)
114 @project_module = name
115 self.instance_eval(&block)
116 @project_module = nil
117 end
118
119 # Returns +true+ if the plugin can be configured.
120 def configurable?
121 settings && settings.is_a?(Hash) && !settings[:partial].blank?
122 end
123 end
124 end
@@ -0,0 +1,15
1 namespace :db do
2 desc 'Migrates installed plugins.'
3 task :migrate_plugins => :environment do
4 if Rails.respond_to?('plugins')
5 Rails.plugins.each do |plugin|
6 next unless plugin.respond_to?('migrate')
7 puts "Migrating #{plugin.name}..."
8 plugin.migrate
9 end
10 else
11 puts "Undefined method plugins for Rails!"
12 puts "Make sure engines plugin is installed."
13 end
14 end
15 end
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,79 +1,80
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class AdminController < ApplicationController
19 19 layout 'base'
20 20 before_filter :require_admin
21 21
22 22 helper :sort
23 23 include SortHelper
24 24
25 25 def index
26 26 end
27 27
28 28 def projects
29 29 sort_init 'name', 'asc'
30 30 sort_update
31 31
32 32 @status = params[:status] ? params[:status].to_i : 0
33 33 conditions = nil
34 34 conditions = ["status=?", @status] unless @status == 0
35 35
36 36 @project_count = Project.count(:conditions => conditions)
37 37 @project_pages = Paginator.new self, @project_count,
38 38 25,
39 39 params['page']
40 40 @projects = Project.find :all, :order => sort_clause,
41 41 :conditions => conditions,
42 42 :limit => @project_pages.items_per_page,
43 43 :offset => @project_pages.current.offset
44 44
45 45 render :action => "projects", :layout => false if request.xhr?
46 46 end
47 47
48 48 def mail_options
49 49 @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
50 50 if request.post?
51 51 Setting.notified_events = (params[:notified_events] || [])
52 52 flash[:notice] = l(:notice_successful_update)
53 53 redirect_to :controller => 'admin', :action => 'mail_options'
54 54 end
55 55 end
56 56
57 57 def test_email
58 58 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
59 59 # Force ActionMailer to raise delivery errors so we can catch it
60 60 ActionMailer::Base.raise_delivery_errors = true
61 61 begin
62 62 @test = Mailer.deliver_test(User.current)
63 63 flash[:notice] = l(:notice_email_sent, User.current.mail)
64 64 rescue Exception => e
65 65 flash[:error] = l(:notice_email_error, e.message)
66 66 end
67 67 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
68 68 redirect_to :action => 'mail_options'
69 69 end
70 70
71 71 def info
72 72 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
73 73 @flags = {
74 74 :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
75 75 :file_repository_writable => File.writable?(Attachment.storage_path),
76 76 :rmagick_available => Object.const_defined?(:Magick)
77 }
77 }
78 @plugins = Redmine::Plugin.registered_plugins
78 79 end
79 80 end
@@ -1,33 +1,45
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class SettingsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :require_admin
21 21
22 22 def index
23 23 edit
24 24 render :action => 'edit'
25 25 end
26 26
27 27 def edit
28 28 if request.post? and params[:settings] and params[:settings].is_a? Hash
29 29 params[:settings].each { |name, value| Setting[name] = value }
30 30 redirect_to :action => 'edit' and return
31 31 end
32 32 end
33
34 def plugin
35 plugin_id = params[:id].to_sym
36 @plugin = Redmine::Plugin.registered_plugins[plugin_id]
37 if request.post?
38 Setting["plugin_#{plugin_id}"] = params[:settings]
39 flash[:notice] = l(:notice_successful_update)
40 redirect_to :action => 'plugin', :id => params[:id]
41 end
42 @partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial]
43 @settings = Setting["plugin_#{plugin_id}"]
44 end
33 45 end
@@ -1,98 +1,102
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Setting < ActiveRecord::Base
19 19
20 20 cattr_accessor :available_settings
21 21 @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
22
22 Redmine::Plugin.registered_plugins.each do |id, plugin|
23 next unless plugin.settings
24 @@available_settings["plugin_#{id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
25 end
26
23 27 validates_uniqueness_of :name
24 28 validates_inclusion_of :name, :in => @@available_settings.keys
25 29 validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
26 30
27 31 # Hash used to cache setting values
28 32 @cached_settings = {}
29 33 @cached_cleared_on = Time.now
30 34
31 35 def value
32 36 v = read_attribute(:value)
33 37 # Unserialize serialized settings
34 38 v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
35 39 v
36 40 end
37 41
38 42 def value=(v)
39 43 v = v.to_yaml if v && @@available_settings[name]['serialized']
40 44 write_attribute(:value, v)
41 45 end
42 46
43 47 # Returns the value of the setting named name
44 48 def self.[](name)
45 49 v = @cached_settings[name]
46 50 v ? v : (@cached_settings[name] = find_or_default(name).value)
47 51 end
48 52
49 53 def self.[]=(name, v)
50 54 setting = find_or_default(name)
51 55 setting.value = (v ? v : "")
52 56 @cached_settings[name] = nil
53 57 setting.save
54 58 setting.value
55 59 end
56 60
57 61 # Defines getter and setter for each setting
58 62 # Then setting values can be read using: Setting.some_setting_name
59 63 # or set using Setting.some_setting_name = "some value"
60 64 @@available_settings.each do |name, params|
61 65 src = <<-END_SRC
62 66 def self.#{name}
63 67 self[:#{name}]
64 68 end
65 69
66 70 def self.#{name}?
67 71 self[:#{name}].to_i > 0
68 72 end
69 73
70 74 def self.#{name}=(value)
71 75 self[:#{name}] = value
72 76 end
73 77 END_SRC
74 78 class_eval src, __FILE__, __LINE__
75 79 end
76 80
77 81 # Checks if settings have changed since the values were read
78 82 # and clears the cache hash if it's the case
79 83 # Called once per request
80 84 def self.check_cache
81 85 settings_updated_on = Setting.maximum(:updated_on)
82 86 if settings_updated_on && @cached_cleared_on <= settings_updated_on
83 87 @cached_settings.clear
84 88 @cached_cleared_on = Time.now
85 89 logger.info "Settings cache cleared." if logger
86 90 end
87 91 end
88 92
89 93 private
90 94 # Returns the Setting instance for the setting named name
91 95 # (record found in database or new record with default value)
92 96 def self.find_or_default(name)
93 97 name = name.to_s
94 98 raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
95 99 setting = find_by_name(name)
96 100 setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
97 101 end
98 102 end
@@ -1,9 +1,25
1 1 <h2><%=l(:label_information_plural)%></h2>
2 2
3 3 <p><%=l(:field_version)%>: <strong><%= Redmine::Info.versioned_name %></strong> (<%= @db_adapter_name %>)</p>
4 4
5 5 <table class="list">
6 6 <tr class="odd"><td>Default administrator account changed</td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
7 7 <tr class="even"><td>File repository writable</td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
8 8 <tr class="odd"><td>RMagick available</td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
9 9 </table>
10
11 <% if @plugins.any? %>
12 &nbsp;
13 <h3 class="icon22 icon22-plugin">Plugins</h3>
14 <table class="list">
15 <% @plugins.keys.sort.each do |plugin| %>
16 <tr class="<%= cycle('odd', 'even') %>">
17 <td><%=h @plugins[plugin].name %></td>
18 <td><%=h @plugins[plugin].description %></td>
19 <td><%=h @plugins[plugin].author %></td>
20 <td><%=h @plugins[plugin].version %></td>
21 <td><%= link_to('Configure', :controller => 'settings', :action => 'plugin', :id => plugin.to_s) if @plugins[plugin].configurable? %></td>
22 </tr>
23 <% end %>
24 </table>
25 <% end %>
@@ -1,87 +1,91
1 1 # Be sure to restart your web server when you modify this file.
2 2
3 3 # Uncomment below to force Rails into production mode when
4 4 # you don't control web/app server and can't set it the proper way
5 5 # ENV['RAILS_ENV'] ||= 'production'
6 6
7 7 # Bootstrap the Rails environment, frameworks, and default configuration
8 8 require File.join(File.dirname(__FILE__), 'boot')
9 9
10 10 Rails::Initializer.run do |config|
11 11 # Settings in config/environments/* take precedence those specified here
12 12
13 13 # Skip frameworks you're not going to use
14 14 # config.frameworks -= [ :action_web_service, :action_mailer ]
15 15
16 16 # Add additional load paths for sweepers
17 17 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
18 18
19 19 # Force all environments to use the same logger level
20 20 # (by default production uses :info, the others :debug)
21 21 # config.log_level = :debug
22 22
23 23 # Use the database for sessions instead of the file system
24 24 # (create the session table with 'rake create_sessions_table')
25 25 # config.action_controller.session_store = :active_record_store
26 26
27 27 # Enable page/fragment caching by setting a file-based store
28 28 # (remember to create the caching directory and make it readable to the application)
29 29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
30 30
31 31 # Activate observers that should always be running
32 32 # config.active_record.observers = :cacher, :garbage_collector
33 33 config.active_record.observers = :message_observer
34 34
35 35 # Make Active Record use UTC-base instead of local time
36 36 # config.active_record.default_timezone = :utc
37 37
38 38 # Use Active Record's schema dumper instead of SQL when creating the test database
39 39 # (enables use of different database adapters for development and test environments)
40 40 # config.active_record.schema_format = :ruby
41 41
42 42 # See Rails::Configuration for more options
43 43
44 44 # SMTP server configuration
45 45 config.action_mailer.smtp_settings = {
46 46 :address => "127.0.0.1",
47 47 :port => 25,
48 48 :domain => "somenet.foo",
49 49 :authentication => :login,
50 50 :user_name => "redmine",
51 51 :password => "redmine",
52 52 }
53 53
54 54 config.action_mailer.perform_deliveries = true
55 55
56 56 # Tell ActionMailer not to deliver emails to the real world.
57 57 # The :test delivery method accumulates sent emails in the
58 58 # ActionMailer::Base.deliveries array.
59 59 #config.action_mailer.delivery_method = :test
60 60 config.action_mailer.delivery_method = :smtp
61
62 # Uncomment this line if the engines plugin is installed.
63 # This will ensure that engines is loaded first.
64 # config.plugins = ["engines", "*"]
61 65 end
62 66
63 67 ActiveRecord::Errors.default_error_messages = {
64 68 :inclusion => "activerecord_error_inclusion",
65 69 :exclusion => "activerecord_error_exclusion",
66 70 :invalid => "activerecord_error_invalid",
67 71 :confirmation => "activerecord_error_confirmation",
68 72 :accepted => "activerecord_error_accepted",
69 73 :empty => "activerecord_error_empty",
70 74 :blank => "activerecord_error_blank",
71 75 :too_long => "activerecord_error_too_long",
72 76 :too_short => "activerecord_error_too_short",
73 77 :wrong_length => "activerecord_error_wrong_length",
74 78 :taken => "activerecord_error_taken",
75 79 :not_a_number => "activerecord_error_not_a_number"
76 80 }
77 81
78 82 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
79 83
80 84 GLoc.set_config :default_language => :en
81 85 GLoc.clear_strings
82 86 GLoc.set_kcode
83 87 GLoc.load_localized_strings
84 88 GLoc.set_config(:raise_string_not_found_errors => false)
85 89
86 90 require 'redmine'
87 91
@@ -1,102 +1,103
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/mime_type'
4 4 require 'redmine/acts_as_watchable/init'
5 5 require 'redmine/acts_as_event/init'
6 require 'redmine/plugin'
6 7
7 8 begin
8 9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 10 rescue LoadError
10 11 # RMagick is not available
11 12 end
12 13
13 14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
14 15
15 16 # Permissions
16 17 Redmine::AccessControl.map do |map|
17 18 map.permission :view_project, {:projects => [:show, :activity, :feeds]}, :public => true
18 19 map.permission :search_project, {:search => :index}, :public => true
19 20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 24
24 25 map.project_module :issue_tracking do |map|
25 26 # Issue categories
26 27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 28 # Issues
28 29 map.permission :view_issues, {:projects => [:list_issues, :export_issues_csv, :export_issues_pdf, :changelog, :roadmap],
29 30 :issues => [:show, :export_pdf],
30 31 :queries => :index,
31 32 :reports => :issue_report}, :public => true
32 33 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
33 34 map.permission :edit_issues, {:issues => [:edit, :destroy_attachment]}, :require => :loggedin
34 35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
35 36 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
36 37 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
37 38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 40 # Queries
40 41 map.permission :manage_pulic_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 43 # Gantt & calendar
43 44 map.permission :view_gantt, :projects => :gantt
44 45 map.permission :view_calendar, :projects => :calendar
45 46 end
46 47
47 48 map.project_module :time_tracking do |map|
48 49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 50 map.permission :view_time_entries, :timelog => [:details, :report]
50 51 end
51 52
52 53 map.project_module :news do |map|
53 54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 55 map.permission :view_news, {:projects => :list_news, :news => :show}, :public => true
55 56 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
56 57 end
57 58
58 59 map.project_module :documents do |map|
59 60 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 61 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
61 62 end
62 63
63 64 map.project_module :files do |map|
64 65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 66 map.permission :view_files, :projects => :list_files, :versions => :download
66 67 end
67 68
68 69 map.project_module :wiki do |map|
69 70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
73 74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 75 end
75 76
76 77 map.project_module :repository do |map|
77 78 map.permission :manage_repository, :repositories => [:edit, :destroy]
78 79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
79 80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 81 end
81 82
82 83 map.project_module :boards do |map|
83 84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 86 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
86 87 end
87 88 end
88 89
89 90 # Project menu configuration
90 91 Redmine::MenuManager.map :project_menu do |menu|
91 92 menu.push :label_overview, :controller => 'projects', :action => 'show'
92 93 menu.push :label_activity, :controller => 'projects', :action => 'activity'
93 94 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
94 95 menu.push :label_issue_plural, :controller => 'projects', :action => 'list_issues'
95 96 menu.push :label_news_plural, :controller => 'projects', :action => 'list_news'
96 97 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
97 98 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
98 99 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
99 100 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
100 101 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
101 102 menu.push :label_settings, :controller => 'projects', :action => 'settings'
102 103 end
@@ -1,448 +1,449
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 7 h4, .wiki h3 {font-size: 11px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 8
9 9 /***** Layout *****/
10 10 #top-menu {background: #22344A;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
11 11 #top-menu a {color: #fff; padding-right: 4px;}
12 12 #account {float:right;}
13 13
14 14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 6px 0px 6px;}
15 15 #header a {color:#f8f8f8;}
16 16 #quick-search {float:right;}
17 17
18 18 #main-menu {position: absolute; top: 5.5em; left:8px;}
19 19 #main-menu ul {margin: 0; padding: 0;}
20 20 #main-menu li {
21 21 float:left;
22 22 list-style-type:none;
23 23 margin: 0px 10px 0px 0px;
24 24 padding: 0px 0px 0px 0px;
25 25 white-space:nowrap;
26 26 }
27 27 #main-menu li a {
28 28 display: block;
29 29 color: #fff;
30 30 text-decoration: none;
31 31 margin: 0;
32 32 padding: 4px 4px 4px 4px;
33 33 background: #22344A;
34 34 }
35 35 #main-menu li a:hover {background:#759FCF;}
36 36
37 37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
38 38
39 39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
40 40 * html #sidebar{ width: 17%; }
41 41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
42 42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
43 43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
44 44
45 45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; position: relative; z-index: 10; height:600px; min-height: 600px;}
46 46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
47 47 html>body #content {
48 48 height: auto;
49 49 min-height: 600px;
50 50 }
51 51
52 52 #main.nosidebar #sidebar{ display: none; }
53 53 #main.nosidebar #content{ width: auto; border-right: 0; }
54 54
55 55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 11px; padding: 5px; text-align:center; background:#fff;}
56 56
57 57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
58 58 #login-form table td {padding: 6px;}
59 59 #login-form label {font-weight: bold;}
60 60
61 61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
62 62
63 63 /***** Links *****/
64 64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
65 65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
66 66 a img{ border: 0; }
67 67
68 68 /***** Tables *****/
69 69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
70 70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
71 71 table.list tbody th { text-align: left; padding: 0px; }
72 72 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
73 73 table.list tbody tr:hover { background-color:#ffffdd; }
74 74 table td {padding:2px;}
75 75 table p {margin:0;}
76 76 .odd {background-color:#f6f7f8;}
77 77 .even {background-color: #fff;}
78 78
79 79 .highlight { background-color: #FCFD8D;}
80 80
81 81 .box{
82 82 padding:6px;
83 83 margin-bottom: 10px;
84 84 background-color:#f6f6f6;
85 85 color:#505050;
86 86 line-height:1.5em;
87 87 border: 1px solid #e4e4e4;
88 88 }
89 89
90 90 div.square {
91 91 border: 1px solid #999;
92 92 float: left;
93 93 margin: .3em .4em 0 .4em;
94 94 overflow: hidden;
95 95 width: .6em; height: .6em;
96 96 }
97 97
98 98 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
99 99 .splitcontentleft{float:left; width:49%;}
100 100 .splitcontentright{float:right; width:49%;}
101 101 form {display: inline;}
102 102 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
103 103 fieldset {border: 1px solid #e4e4e4; margin:0;}
104 104 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
105 105 textarea.wiki-edit { width: 99%; }
106 106 li p {margin-top: 0;}
107 107 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
108 108 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
109 109
110 110 /***** Tabular forms ******/
111 111 .tabular p{
112 112 margin: 0;
113 113 padding: 5px 0 8px 0;
114 114 padding-left: 180px; /*width of left column containing the label elements*/
115 115 height: 1%;
116 116 clear:left;
117 117 }
118 118
119 119 .tabular label{
120 120 font-weight: bold;
121 121 float: left;
122 122 text-align: right;
123 123 margin-left: -180px; /*width of left column*/
124 124 width: 175px; /*width of labels. Should be smaller than left column to create some right
125 125 margin*/
126 126 }
127 127
128 128 #settings .tabular p{ padding-left: 300px; }
129 129 #settings .tabular label{ margin-left: -300px; width: 295px; }
130 130
131 131 .required {color: #bb0000;}
132 132 .summary {font-style: italic;}
133 133
134 134 /***** Flash & error messages ****/
135 135 #flash div, #errorExplanation, .nodata {
136 136 padding: 4px 4px 4px 30px;
137 137 margin-bottom: 12px;
138 138 font-size: 1.1em;
139 139 border: 2px solid;
140 140 }
141 141
142 142 #flash div {margin-top: 6px;}
143 143
144 144 #flash div.error, #errorExplanation {
145 145 background: url(../images/false.png) 8px 5px no-repeat;
146 146 background-color: #ffe3e3;
147 147 border-color: #dd0000;
148 148 color: #550000;
149 149 }
150 150
151 151 #flash div.notice {
152 152 background: url(../images/true.png) 8px 5px no-repeat;
153 153 background-color: #dfffdf;
154 154 border-color: #9fcf9f;
155 155 color: #005f00;
156 156 }
157 157
158 158 .nodata {
159 159 text-align: center;
160 160 background-color: #FFEBC1;
161 161 border-color: #FDBF3B;
162 162 color: #A6750C;
163 163 }
164 164
165 165 #errorExplanation ul { font-size: 0.9em;}
166 166
167 167 /***** Ajax indicator ******/
168 168 #ajax-indicator {
169 169 position: absolute; /* fixed not supported by IE */
170 170 background-color:#eee;
171 171 border: 1px solid #bbb;
172 172 top:35%;
173 173 left:40%;
174 174 width:20%;
175 175 font-weight:bold;
176 176 text-align:center;
177 177 padding:0.6em;
178 178 z-index:100;
179 179 filter:alpha(opacity=50);
180 180 -moz-opacity:0.5;
181 181 opacity: 0.5;
182 182 -khtml-opacity: 0.5;
183 183 }
184 184
185 185 html>body #ajax-indicator { position: fixed; }
186 186
187 187 #ajax-indicator span {
188 188 background-position: 0% 40%;
189 189 background-repeat: no-repeat;
190 190 background-image: url(../images/loading.gif);
191 191 padding-left: 26px;
192 192 vertical-align: bottom;
193 193 }
194 194
195 195 /***** Calendar *****/
196 196 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
197 197 table.cal th { background-color:#EEEEEE; padding: 4px; }
198 198 table.cal td {border: 1px solid #d7d7d7;}
199 199 table.cal td.today {background:#ffffdd;}
200 200
201 201 /***** Tooltips ******/
202 202 .tooltip{position:relative;z-index:24;}
203 203 .tooltip:hover{z-index:25;color:#000;}
204 204 .tooltip span.tip{display: none; text-align:left;}
205 205
206 206 div.tooltip:hover span.tip{
207 207 display:block;
208 208 position:absolute;
209 209 top:12px; left:24px; width:270px;
210 210 border:1px solid #555;
211 211 background-color:#fff;
212 212 padding: 4px;
213 213 font-size: 0.8em;
214 214 color:#505050;
215 215 }
216 216
217 217 /***** Progress bar *****/
218 218 .progress {
219 219 border: 1px solid #D7D7D7;
220 220 border-collapse: collapse;
221 221 border-spacing: 0pt;
222 222 empty-cells: show;
223 223 padding: 3px;
224 224 width: 40em;
225 225 text-align: center;
226 226 }
227 227
228 228 .progress td { height: 1em; }
229 229 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
230 230 .progress .open { background: #FFF none repeat scroll 0%; }
231 231
232 232 /***** Tabs *****/
233 233 #content .tabs{height: 2.6em;}
234 234 #content .tabs ul{margin:0;}
235 235 #content .tabs ul li{
236 236 float:left;
237 237 list-style-type:none;
238 238 white-space:nowrap;
239 239 margin-right:8px;
240 240 background:#fff;
241 241 }
242 242 #content .tabs ul li a{
243 243 display:block;
244 244 font-size: 0.9em;
245 245 text-decoration:none;
246 246 line-height:1em;
247 247 padding:4px;
248 248 border: 1px solid #c0c0c0;
249 249 }
250 250
251 251 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
252 252 background-color: #507AAA;
253 253 border: 1px solid #507AAA;
254 254 color: #fff;
255 255 text-decoration:none;
256 256 }
257 257
258 258 /***** Diff *****/
259 259 .diff_out { background: #fcc; }
260 260 .diff_in { background: #cfc; }
261 261
262 262 /***** Wiki *****/
263 263 div.wiki table {
264 264 border: 1px solid #505050;
265 265 border-collapse: collapse;
266 266 }
267 267
268 268 div.wiki table, div.wiki td, div.wiki th {
269 269 border: 1px solid #bbb;
270 270 padding: 4px;
271 271 }
272 272
273 273 div.wiki a {
274 274 background-position: 0% 60%;
275 275 background-repeat: no-repeat;
276 276 padding-left: 12px;
277 277 background-image: url(../images/external.png);
278 278 }
279 279
280 280 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset, div.wiki a.email, div.wiki div.toc a {
281 281 padding-left: 0;
282 282 background-image: none;
283 283 }
284 284
285 285 div.wiki a.new {
286 286 color: #b73535;
287 287 }
288 288
289 289 div.wiki pre {
290 290 margin: 1em 1em 1em 1.6em;
291 291 padding: 2px;
292 292 background-color: #fafafa;
293 293 border: 1px solid #dadada;
294 294 width:95%;
295 295 overflow-x: auto;
296 296 }
297 297
298 298 div.wiki div.toc {
299 299 background-color: #ffffdd;
300 300 border: 1px solid #e4e4e4;
301 301 padding: 4px;
302 302 line-height: 1.2em;
303 303 margin-bottom: 12px;
304 304 margin-right: 12px;
305 305 float: left;
306 306 }
307 307
308 308 div.wiki div.toc.right {
309 309 float: right;
310 310 margin-left: 12px;
311 311 margin-right: 0;
312 312 }
313 313
314 314 div.wiki div.toc a {
315 315 display: block;
316 316 font-size: 0.9em;
317 317 font-weight: normal;
318 318 text-decoration: none;
319 319 color: #606060;
320 320 }
321 321 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
322 322
323 323 div.wiki div.toc a.heading2 { margin-left: 6px; }
324 324 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
325 325
326 326 /***** My page layout *****/
327 327 .block-receiver {
328 328 border:1px dashed #c0c0c0;
329 329 margin-bottom: 20px;
330 330 padding: 15px 0 15px 0;
331 331 }
332 332
333 333 .mypage-box {
334 334 margin:0 0 20px 0;
335 335 color:#505050;
336 336 line-height:1.5em;
337 337 }
338 338
339 339 .handle {
340 340 cursor: move;
341 341 }
342 342
343 343 a.close-icon {
344 344 display:block;
345 345 margin-top:3px;
346 346 overflow:hidden;
347 347 width:12px;
348 348 height:12px;
349 349 background-repeat: no-repeat;
350 350 cursor:pointer;
351 351 background-image:url('../images/close.png');
352 352 }
353 353
354 354 a.close-icon:hover {
355 355 background-image:url('../images/close_hl.png');
356 356 }
357 357
358 358 /***** Gantt chart *****/
359 359 .gantt_hdr {
360 360 position:absolute;
361 361 top:0;
362 362 height:16px;
363 363 border-top: 1px solid #c0c0c0;
364 364 border-bottom: 1px solid #c0c0c0;
365 365 border-right: 1px solid #c0c0c0;
366 366 text-align: center;
367 367 overflow: hidden;
368 368 }
369 369
370 370 .task {
371 371 position: absolute;
372 372 height:8px;
373 373 font-size:0.8em;
374 374 color:#888;
375 375 padding:0;
376 376 margin:0;
377 377 line-height:0.8em;
378 378 }
379 379
380 380 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
381 381 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
382 382 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
383 383 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
384 384
385 385 /***** Icons *****/
386 386 .icon {
387 387 background-position: 0% 40%;
388 388 background-repeat: no-repeat;
389 389 padding-left: 20px;
390 390 padding-top: 2px;
391 391 padding-bottom: 3px;
392 392 }
393 393
394 394 .icon22 {
395 395 background-position: 0% 40%;
396 396 background-repeat: no-repeat;
397 397 padding-left: 26px;
398 398 line-height: 22px;
399 399 vertical-align: middle;
400 400 }
401 401
402 402 .icon-add { background-image: url(../images/add.png); }
403 403 .icon-edit { background-image: url(../images/edit.png); }
404 404 .icon-del { background-image: url(../images/delete.png); }
405 405 .icon-move { background-image: url(../images/move.png); }
406 406 .icon-save { background-image: url(../images/save.png); }
407 407 .icon-cancel { background-image: url(../images/cancel.png); }
408 408 .icon-pdf { background-image: url(../images/pdf.png); }
409 409 .icon-csv { background-image: url(../images/csv.png); }
410 410 .icon-html { background-image: url(../images/html.png); }
411 411 .icon-image { background-image: url(../images/image.png); }
412 412 .icon-txt { background-image: url(../images/txt.png); }
413 413 .icon-file { background-image: url(../images/file.png); }
414 414 .icon-folder { background-image: url(../images/folder.png); }
415 415 .icon-package { background-image: url(../images/package.png); }
416 416 .icon-home { background-image: url(../images/home.png); }
417 417 .icon-user { background-image: url(../images/user.png); }
418 418 .icon-mypage { background-image: url(../images/user_page.png); }
419 419 .icon-admin { background-image: url(../images/admin.png); }
420 420 .icon-projects { background-image: url(../images/projects.png); }
421 421 .icon-logout { background-image: url(../images/logout.png); }
422 422 .icon-help { background-image: url(../images/help.png); }
423 423 .icon-attachment { background-image: url(../images/attachment.png); }
424 424 .icon-index { background-image: url(../images/index.png); }
425 425 .icon-history { background-image: url(../images/history.png); }
426 426 .icon-feed { background-image: url(../images/feed.png); }
427 427 .icon-time { background-image: url(../images/time.png); }
428 428 .icon-stats { background-image: url(../images/stats.png); }
429 429 .icon-warning { background-image: url(../images/warning.png); }
430 430 .icon-fav { background-image: url(../images/fav.png); }
431 431 .icon-fav-off { background-image: url(../images/fav_off.png); }
432 432 .icon-reload { background-image: url(../images/reload.png); }
433 433 .icon-lock { background-image: url(../images/locked.png); }
434 434 .icon-unlock { background-image: url(../images/unlock.png); }
435 435 .icon-note { background-image: url(../images/note.png); }
436 436
437 437 .icon22-projects { background-image: url(../images/22x22/projects.png); }
438 438 .icon22-users { background-image: url(../images/22x22/users.png); }
439 439 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
440 440 .icon22-role { background-image: url(../images/22x22/role.png); }
441 441 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
442 442 .icon22-options { background-image: url(../images/22x22/options.png); }
443 443 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
444 444 .icon22-authent { background-image: url(../images/22x22/authent.png); }
445 445 .icon22-info { background-image: url(../images/22x22/info.png); }
446 446 .icon22-comment { background-image: url(../images/22x22/comment.png); }
447 447 .icon22-package { background-image: url(../images/22x22/package.png); }
448 448 .icon22-settings { background-image: url(../images/22x22/settings.png); }
449 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
@@ -1,134 +1,134
1 1 # Copyright (c) 2005-2006 David Barri
2 2
3 3 require 'iconv'
4 4 require 'gloc-version'
5 5
6 6 module GLoc
7 7 class GLocError < StandardError #:nodoc:
8 8 end
9 9 class InvalidArgumentsError < GLocError #:nodoc:
10 10 end
11 11 class InvalidKeyError < GLocError #:nodoc:
12 12 end
13 13 class RuleNotFoundError < GLocError #:nodoc:
14 14 end
15 15 class StringNotFoundError < GLocError #:nodoc:
16 16 end
17 17
18 18 class << self
19 19 private
20 20
21 21 def _add_localized_data(lang, symbol_hash, override, target) #:nodoc:
22 22 lang= lang.to_sym
23 23 if override
24 24 target[lang] ||= {}
25 25 target[lang].merge!(symbol_hash)
26 26 else
27 27 symbol_hash.merge!(target[lang]) if target[lang]
28 28 target[lang]= symbol_hash
29 29 end
30 30 end
31 31
32 32 def _add_localized_strings(lang, symbol_hash, override=true, strings_charset=nil) #:nodoc:
33 33 _charset_required
34 34
35 35 # Convert all incoming strings to the gloc charset
36 36 if strings_charset
37 37 Iconv.open(get_charset(lang), strings_charset) do |i|
38 38 symbol_hash.each_pair {|k,v| symbol_hash[k]= i.iconv(v)}
39 39 end
40 40 end
41 41
42 42 # Convert rules
43 43 rules= {}
44 44 old_kcode= $KCODE
45 45 begin
46 46 $KCODE= 'u'
47 47 Iconv.open(UTF_8, get_charset(lang)) do |i|
48 48 symbol_hash.each {|k,v|
49 49 if /^_gloc_rule_(.+)$/ =~ k.to_s
50 50 v= i.iconv(v) if v
51 51 v= '""' if v.nil?
52 52 rules[$1.to_sym]= eval "Proc.new do #{v} end"
53 53 end
54 54 }
55 55 end
56 56 ensure
57 57 $KCODE= old_kcode
58 58 end
59 59 rules.keys.each {|k| symbol_hash.delete "_gloc_rule_#{k}".to_sym}
60 60
61 61 # Add new localized data
62 62 LOWERCASE_LANGUAGES[lang.to_s.downcase]= lang
63 63 _add_localized_data(lang, symbol_hash, override, LOCALIZED_STRINGS)
64 64 _add_localized_data(lang, rules, override, RULES)
65 65 end
66 66
67 67 def _charset_required #:nodoc:
68 68 set_charset UTF_8 unless CONFIG[:internal_charset]
69 69 end
70 70
71 71 def _get_internal_state_vars
72 72 [ CONFIG, LOCALIZED_STRINGS, RULES, LOWERCASE_LANGUAGES ]
73 73 end
74 74
75 75 def _get_lang_file_list(dir) #:nodoc:
76 dir= File.join(RAILS_ROOT,'lang') if dir.nil?
76 dir= File.join(RAILS_ROOT,'{.,vendor/plugins/*}','lang') if dir.nil?
77 77 Dir[File.join(dir,'*.{yaml,yml}')]
78 78 end
79 79
80 80 def _l(symbol, language, *arguments) #:nodoc:
81 81 symbol= symbol.to_sym if symbol.is_a?(String)
82 82 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
83 83
84 84 translation= LOCALIZED_STRINGS[language][symbol] rescue nil
85 85 if translation.nil?
86 86 raise StringNotFoundError.new("There is no key called '#{symbol}' in the #{language} strings.") if CONFIG[:raise_string_not_found_errors]
87 87 translation= symbol.to_s
88 88 end
89 89
90 90 begin
91 91 return translation % arguments
92 92 rescue => e
93 93 raise InvalidArgumentsError.new("Translation value #{translation.inspect} with arguments #{arguments.inspect} caused error '#{e.message}'")
94 94 end
95 95 end
96 96
97 97 def _l_has_string?(symbol,lang) #:nodoc:
98 98 symbol= symbol.to_sym if symbol.is_a?(String)
99 99 LOCALIZED_STRINGS[lang].has_key?(symbol.to_sym) rescue false
100 100 end
101 101
102 102 def _l_rule(symbol,lang) #:nodoc:
103 103 symbol= symbol.to_sym if symbol.is_a?(String)
104 104 raise InvalidKeyError.new("Symbol or String expected as key.") unless symbol.kind_of?(Symbol)
105 105
106 106 r= RULES[lang][symbol] rescue nil
107 107 raise RuleNotFoundError.new("There is no rule called '#{symbol}' in the #{lang} rules.") if r.nil?
108 108 r
109 109 end
110 110
111 111 def _verbose_msg(type=nil)
112 112 return unless CONFIG[:verbose]
113 113 x= case type
114 114 when :stats
115 115 x= valid_languages.map{|l| ":#{l}(#{LOCALIZED_STRINGS[l].size}/#{RULES[l].size})"}.sort.join(', ')
116 116 "Current stats -- #{x}"
117 117 else
118 118 yield
119 119 end
120 120 puts "[GLoc] #{x}"
121 121 end
122 122
123 123 public :_l, :_l_has_string?, :_l_rule
124 124 end
125 125
126 126 private
127 127
128 128 unless const_defined?(:LOCALIZED_STRINGS)
129 129 LOCALIZED_STRINGS= {}
130 130 RULES= {}
131 131 LOWERCASE_LANGUAGES= {}
132 132 end
133 133
134 134 end
General Comments 0
You need to be logged in to leave comments. Login now