Revision 1298:4f746d8966dd .svn/pristine/3e

View differences:

.svn/pristine/3e/3e17a1388b20cd76503961d40ea60716108de247.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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
require File.expand_path('../../test_helper', __FILE__)
19

  
20
class IssueRelationsControllerTest < ActionController::TestCase
21
  fixtures :projects,
22
           :users,
23
           :roles,
24
           :members,
25
           :member_roles,
26
           :issues,
27
           :issue_statuses,
28
           :issue_relations,
29
           :enabled_modules,
30
           :enumerations,
31
           :trackers,
32
           :projects_trackers
33

  
34
  def setup
35
    User.current = nil
36
    @request.session[:user_id] = 3
37
  end
38

  
39
  def test_create
40
    assert_difference 'IssueRelation.count' do
41
      post :create, :issue_id => 1,
42
                 :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
43
    end
44
    relation = IssueRelation.first(:order => 'id DESC')
45
    assert_equal 1, relation.issue_from_id
46
    assert_equal 2, relation.issue_to_id
47
    assert_equal 'relates', relation.relation_type
48
  end
49

  
50
  def test_create_xhr
51
    assert_difference 'IssueRelation.count' do
52
      xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '1', :relation_type => 'relates', :delay => ''}
53
      assert_response :success
54
      assert_template 'create'
55
      assert_equal 'text/javascript', response.content_type
56
    end
57
    relation = IssueRelation.first(:order => 'id DESC')
58
    assert_equal 3, relation.issue_from_id
59
    assert_equal 1, relation.issue_to_id
60

  
61
    assert_match /Bug #1/, response.body
62
  end
63

  
64
  def test_create_should_accept_id_with_hash
65
    assert_difference 'IssueRelation.count' do
66
      post :create, :issue_id => 1,
67
                 :relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
68
    end
69
    relation = IssueRelation.first(:order => 'id DESC')
70
    assert_equal 2, relation.issue_to_id
71
  end
72

  
73
  def test_create_should_strip_id
74
    assert_difference 'IssueRelation.count' do
75
      post :create, :issue_id => 1,
76
                 :relation => {:issue_to_id => ' 2  ', :relation_type => 'relates', :delay => ''}
77
    end
78
    relation = IssueRelation.first(:order => 'id DESC')
79
    assert_equal 2, relation.issue_to_id
80
  end
81

  
82
  def test_create_should_not_break_with_non_numerical_id
83
    assert_no_difference 'IssueRelation.count' do
84
      assert_nothing_raised do
85
        post :create, :issue_id => 1,
86
                   :relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''}
87
      end
88
    end
89
  end
90

  
91
  def test_create_follows_relation_should_update_relations_list
92
    issue1 = Issue.generate!(:subject => 'Followed issue', :start_date => Date.yesterday, :due_date => Date.today)
93
    issue2 = Issue.generate!
94

  
95
    assert_difference 'IssueRelation.count' do
96
      xhr :post, :create, :issue_id => issue2.id,
97
                 :relation => {:issue_to_id => issue1.id, :relation_type => 'follows', :delay => ''}
98
    end
99
    assert_match /Followed issue/, response.body
100
  end
101

  
102
  def test_should_create_relations_with_visible_issues_only
103
    Setting.cross_project_issue_relations = '1'
104
    assert_nil Issue.visible(User.find(3)).find_by_id(4)
105

  
106
    assert_no_difference 'IssueRelation.count' do
107
      post :create, :issue_id => 1,
108
                 :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''}
109
    end
110
  end
111

  
112
  should "prevent relation creation when there's a circular dependency"
113

  
114
  def test_create_xhr_with_failure
115
    assert_no_difference 'IssueRelation.count' do
116
      xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '999', :relation_type => 'relates', :delay => ''}
117

  
118
      assert_response :success
119
      assert_template 'create'
120
      assert_equal 'text/javascript', response.content_type
121
    end
122

  
123
    assert_match /errorExplanation/, response.body
124
  end
125

  
126
  def test_destroy
127
    assert_difference 'IssueRelation.count', -1 do
128
      delete :destroy, :id => '2'
129
    end
130
  end
131

  
132
  def test_destroy_xhr
133
    IssueRelation.create!(:relation_type => IssueRelation::TYPE_RELATES) do |r|
134
      r.issue_from_id = 3
135
      r.issue_to_id = 1
136
    end
137

  
138
    assert_difference 'IssueRelation.count', -1 do
139
      xhr :delete, :destroy, :id => '2'
140

  
141
      assert_response :success
142
      assert_template 'destroy'
143
      assert_equal 'text/javascript', response.content_type
144
      assert_match /relation-2/, response.body
145
    end
146
  end
147
end
.svn/pristine/3e/3e2401296aa57bb537864ac16aaa5ce36ba06138.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2013  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
require File.expand_path('../../../test_helper', __FILE__)
19

  
20
class Redmine::ApiTest::MembershipsTest < Redmine::ApiTest::Base
21
  fixtures :projects, :users, :roles, :members, :member_roles
22

  
23
  def setup
24
    Setting.rest_api_enabled = '1'
25
  end
26

  
27
  context "/projects/:project_id/memberships" do
28
    context "GET" do
29
      context "xml" do
30
        should "return memberships" do
31
          get '/projects/1/memberships.xml', {}, credentials('jsmith')
32

  
33
          assert_response :success
34
          assert_equal 'application/xml', @response.content_type
35
          assert_tag :tag => 'memberships',
36
            :attributes => {:type => 'array'},
37
            :child => {
38
              :tag => 'membership',
39
              :child => {
40
                :tag => 'id',
41
                :content => '2',
42
                :sibling => {
43
                  :tag => 'user',
44
                  :attributes => {:id => '3', :name => 'Dave Lopper'},
45
                  :sibling => {
46
                    :tag => 'roles',
47
                    :child => {
48
                      :tag => 'role',
49
                      :attributes => {:id => '2', :name => 'Developer'}
50
                    }
51
                  }
52
                }
53
              }
54
            }
55
        end
56
      end
57

  
58
      context "json" do
59
        should "return memberships" do
60
          get '/projects/1/memberships.json', {}, credentials('jsmith')
61

  
62
          assert_response :success
63
          assert_equal 'application/json', @response.content_type
64
          json = ActiveSupport::JSON.decode(response.body)
65
          assert_equal({
66
            "memberships" =>
67
              [{"id"=>1,
68
                "project" => {"name"=>"eCookbook", "id"=>1},
69
                "roles" => [{"name"=>"Manager", "id"=>1}],
70
                "user" => {"name"=>"John Smith", "id"=>2}},
71
               {"id"=>2,
72
                "project" => {"name"=>"eCookbook", "id"=>1},
73
                "roles" => [{"name"=>"Developer", "id"=>2}],
74
                "user" => {"name"=>"Dave Lopper", "id"=>3}}],
75
           "limit" => 25,
76
           "total_count" => 2,
77
           "offset" => 0},
78
           json)
79
        end
80
      end
81
    end
82

  
83
    context "POST" do
84
      context "xml" do
85
        should "create membership" do
86
          assert_difference 'Member.count' do
87
            post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith')
88

  
89
            assert_response :created
90
          end
91
        end
92

  
93
        should "return errors on failure" do
94
          assert_no_difference 'Member.count' do
95
            post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith')
96

  
97
            assert_response :unprocessable_entity
98
            assert_equal 'application/xml', @response.content_type
99
            assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"}
100
          end
101
        end
102
      end
103
    end
104
  end
105

  
106
  context "/memberships/:id" do
107
    context "GET" do
108
      context "xml" do
109
        should "return the membership" do
110
          get '/memberships/2.xml', {}, credentials('jsmith')
111

  
112
          assert_response :success
113
          assert_equal 'application/xml', @response.content_type
114
          assert_tag :tag => 'membership',
115
            :child => {
116
              :tag => 'id',
117
              :content => '2',
118
              :sibling => {
119
                :tag => 'user',
120
                :attributes => {:id => '3', :name => 'Dave Lopper'},
121
                :sibling => {
122
                  :tag => 'roles',
123
                  :child => {
124
                    :tag => 'role',
125
                    :attributes => {:id => '2', :name => 'Developer'}
126
                  }
127
                }
128
              }
129
            }
130
        end
131
      end
132

  
133
      context "json" do
134
        should "return the membership" do
135
          get '/memberships/2.json', {}, credentials('jsmith')
136

  
137
          assert_response :success
138
          assert_equal 'application/json', @response.content_type
139
          json = ActiveSupport::JSON.decode(response.body)
140
          assert_equal(
141
            {"membership" => {
142
              "id" => 2,
143
              "project" => {"name"=>"eCookbook", "id"=>1},
144
              "roles" => [{"name"=>"Developer", "id"=>2}],
145
              "user" => {"name"=>"Dave Lopper", "id"=>3}}
146
            },
147
            json)
148
        end
149
      end
150
    end
151

  
152
    context "PUT" do
153
      context "xml" do
154
        should "update membership" do
155
          assert_not_equal [1,2], Member.find(2).role_ids.sort
156
          assert_no_difference 'Member.count' do
157
            put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith')
158

  
159
            assert_response :ok
160
            assert_equal '', @response.body
161
          end
162
          member = Member.find(2)
163
          assert_equal [1,2], member.role_ids.sort
164
        end
165

  
166
        should "return errors on failure" do
167
          put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith')
168

  
169
          assert_response :unprocessable_entity
170
          assert_equal 'application/xml', @response.content_type
171
          assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/}
172
        end
173
      end
174
    end
175

  
176
    context "DELETE" do
177
      context "xml" do
178
        should "destroy membership" do
179
          assert_difference 'Member.count', -1 do
180
            delete '/memberships/2.xml', {}, credentials('jsmith')
181

  
182
            assert_response :ok
183
            assert_equal '', @response.body
184
          end
185
          assert_nil Member.find_by_id(2)
186
        end
187

  
188
        should "respond with 422 on failure" do
189
          assert_no_difference 'Member.count' do
190
            # A membership with an inherited role can't be deleted
191
            Member.find(2).member_roles.first.update_attribute :inherited_from, 99
192
            delete '/memberships/2.xml', {}, credentials('jsmith')
193

  
194
            assert_response :unprocessable_entity
195
          end
196
        end
197
      end
198
    end
199
  end
200
end
.svn/pristine/3e/3e2856705bc7c004aa221bf2a7acea9b1ba0b99e.svn-base
1
module CollectiveIdea #:nodoc:
2
  module Acts #:nodoc:
3
    module NestedSet #:nodoc:
4
      def self.included(base)
5
        base.extend(SingletonMethods)
6
      end
7

  
8
      # This acts provides Nested Set functionality. Nested Set is a smart way to implement
9
      # an _ordered_ tree, with the added feature that you can select the children and all of their
10
      # descendants with a single query. The drawback is that insertion or move need some complex
11
      # sql queries. But everything is done here by this module!
12
      #
13
      # Nested sets are appropriate each time you want either an orderd tree (menus,
14
      # commercial categories) or an efficient way of querying big trees (threaded posts).
15
      #
16
      # == API
17
      #
18
      # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one
19
      # by another easier, except for the creation:
20
      #
21
      # in acts_as_tree:
22
      #   item.children.create(:name => "child1")
23
      #
24
      # in acts_as_nested_set:
25
      #   # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
26
      #   child = MyClass.new(:name => "child1")
27
      #   child.save
28
      #   # now move the item to its right place
29
      #   child.move_to_child_of my_item
30
      #
31
      # You can pass an id or an object to:
32
      # * <tt>#move_to_child_of</tt>
33
      # * <tt>#move_to_right_of</tt>
34
      # * <tt>#move_to_left_of</tt>
35
      #
36
      module SingletonMethods
37
        # Configuration options are:
38
        #
39
        # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
40
        # * +:left_column+ - column name for left boundry data, default "lft"
41
        # * +:right_column+ - column name for right boundry data, default "rgt"
42
        # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
43
        #   (if it hasn't been already) and use that as the foreign key restriction. You
44
        #   can also pass an array to scope by multiple attributes.
45
        #   Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
46
        # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
47
        #   child objects are destroyed alongside this object by calling their destroy
48
        #   method. If set to :delete_all (default), all the child objects are deleted
49
        #   without calling their destroy method.
50
        #
51
        # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
52
        # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added 
53
        # to acts_as_nested_set models
54
        def acts_as_nested_set(options = {})
55
          options = {
56
            :parent_column => 'parent_id',
57
            :left_column => 'lft',
58
            :right_column => 'rgt',
59
            :order => 'id',
60
            :dependent => :delete_all, # or :destroy
61
          }.merge(options)
62
          
63
          if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
64
            options[:scope] = "#{options[:scope]}_id".intern
65
          end
66

  
67
          write_inheritable_attribute :acts_as_nested_set_options, options
68
          class_inheritable_reader :acts_as_nested_set_options
69
          
70
          include Comparable
71
          include Columns
72
          include InstanceMethods
73
          extend Columns
74
          extend ClassMethods
75

  
76
          # no bulk assignment
77
          attr_protected  left_column_name.intern,
78
                          right_column_name.intern, 
79
                          parent_column_name.intern
80
                          
81
          before_create :set_default_left_and_right
82
          before_destroy :prune_from_tree
83
                          
84
          # no assignment to structure fields
85
          [left_column_name, right_column_name, parent_column_name].each do |column|
86
            module_eval <<-"end_eval", __FILE__, __LINE__
87
              def #{column}=(x)
88
                raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
89
              end
90
            end_eval
91
          end
92
          
93
          named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
94
          named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
95
          if self.respond_to?(:define_callbacks)
96
            define_callbacks("before_move", "after_move")              
97
          end
98

  
99
          
100
        end
101
        
102
      end
103
      
104
      module ClassMethods
105
        
106
        # Returns the first root
107
        def root
108
          roots.find(:first)
109
        end
110
        
111
        def valid?
112
          left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
113
        end
114
        
115
        def left_and_rights_valid?
116
          count(
117
            :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
118
              "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
119
            :conditions =>
120
              "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
121
              "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
122
              "#{quoted_table_name}.#{quoted_left_column_name} >= " +
123
                "#{quoted_table_name}.#{quoted_right_column_name} OR " +
124
              "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
125
                "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
126
                "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
127
          ) == 0
128
        end
129
        
130
        def no_duplicates_for_columns?
131
          scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
132
            connection.quote_column_name(c)
133
          end.push(nil).join(", ")
134
          [quoted_left_column_name, quoted_right_column_name].all? do |column|
135
            # No duplicates
136
            find(:first, 
137
              :select => "#{scope_string}#{column}, COUNT(#{column})", 
138
              :group => "#{scope_string}#{column} 
139
                HAVING COUNT(#{column}) > 1").nil?
140
          end
141
        end
142
        
143
        # Wrapper for each_root_valid? that can deal with scope.
144
        def all_roots_valid?
145
          if acts_as_nested_set_options[:scope]
146
            roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
147
              each_root_valid?(grouped_roots)
148
            end
149
          else
150
            each_root_valid?(roots)
151
          end
152
        end
153
        
154
        def each_root_valid?(roots_to_validate)
155
          left = right = 0
156
          roots_to_validate.all? do |root|
157
            (root.left > left && root.right > right).tap do
158
              left = root.left
159
              right = root.right
160
            end
161
          end
162
        end
163
                
164
        # Rebuilds the left & rights if unset or invalid.  Also very useful for converting from acts_as_tree.
165
        def rebuild!(force=false)
166
          # Don't rebuild a valid tree.
167
          # valid? doesn't strictly validate the tree
168
          return true if !force && valid?
169
          
170
          scope = lambda{|node|}
171
          if acts_as_nested_set_options[:scope]
172
            scope = lambda{|node| 
173
              scope_column_names.inject(""){|str, column_name|
174
                str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
175
              }
176
            }
177
          end
178
          indices = {}
179
          
180
          set_left_and_rights = lambda do |node|
181
            # set left
182
            node[left_column_name] = indices[scope.call(node)] += 1
183
            # find
184
            find(:all, :conditions => ["#{quoted_parent_column_name} = ? #{scope.call(node)}", node], :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{acts_as_nested_set_options[:order]}").each{|n| set_left_and_rights.call(n) }
185
            # set right
186
            node[right_column_name] = indices[scope.call(node)] += 1    
187
            node.save!    
188
          end
189
                              
190
          # Find root node(s)
191
          root_nodes = find(:all, :conditions => "#{quoted_parent_column_name} IS NULL", :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{acts_as_nested_set_options[:order]}").each do |root_node|
192
            # setup index for this scope
193
            indices[scope.call(root_node)] ||= 0
194
            set_left_and_rights.call(root_node)
195
          end
196
        end
197
      end
198
      
199
      # Mixed into both classes and instances to provide easy access to the column names
200
      module Columns
201
        def left_column_name
202
          acts_as_nested_set_options[:left_column]
203
        end
204
        
205
        def right_column_name
206
          acts_as_nested_set_options[:right_column]
207
        end
208
        
209
        def parent_column_name
210
          acts_as_nested_set_options[:parent_column]
211
        end
212
        
213
        def scope_column_names
214
          Array(acts_as_nested_set_options[:scope])
215
        end
216
        
217
        def quoted_left_column_name
218
          connection.quote_column_name(left_column_name)
219
        end
220
        
221
        def quoted_right_column_name
222
          connection.quote_column_name(right_column_name)
223
        end
224
        
225
        def quoted_parent_column_name
226
          connection.quote_column_name(parent_column_name)
227
        end
228
        
229
        def quoted_scope_column_names
230
          scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
231
        end
232
      end
233

  
234
      # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
235
      #
236
      #   category.self_and_descendants.count
237
      #   category.ancestors.find(:all, :conditions => "name like '%foo%'")
238
      module InstanceMethods
239
        # Value of the parent column
240
        def parent_id
241
          self[parent_column_name]
242
        end
243
        
244
        # Value of the left column
245
        def left
246
          self[left_column_name]
247
        end
248
        
249
        # Value of the right column
250
        def right
251
          self[right_column_name]
252
        end
253

  
254
        # Returns true if this is a root node.
255
        def root?
256
          parent_id.nil?
257
        end
258
        
259
        def leaf?
260
          new_record? || (right - left == 1)
261
        end
262

  
263
        # Returns true is this is a child node
264
        def child?
265
          !parent_id.nil?
266
        end
267

  
268
        # order by left column
269
        def <=>(x)
270
          left <=> x.left
271
        end
272
        
273
        # Redefine to act like active record
274
        def ==(comparison_object)
275
          comparison_object.equal?(self) ||
276
            (comparison_object.instance_of?(self.class) &&
277
              comparison_object.id == id &&
278
              !comparison_object.new_record?)
279
        end
280

  
281
        # Returns root
282
        def root
283
          self_and_ancestors.find(:first)
284
        end
285

  
286
        # Returns the immediate parent
287
        def parent
288
          nested_set_scope.find_by_id(parent_id) if parent_id
289
        end
290

  
291
        # Returns the array of all parents and self
292
        def self_and_ancestors
293
          nested_set_scope.scoped :conditions => [
294
            "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right
295
          ]
296
        end
297

  
298
        # Returns an array of all parents
299
        def ancestors
300
          without_self self_and_ancestors
301
        end
302

  
303
        # Returns the array of all children of the parent, including self
304
        def self_and_siblings
305
          nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
306
        end
307

  
308
        # Returns the array of all children of the parent, except self
309
        def siblings
310
          without_self self_and_siblings
311
        end
312

  
313
        # Returns a set of all of its nested children which do not have children  
314
        def leaves
315
          descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1"
316
        end    
317

  
318
        # Returns the level of this object in the tree
319
        # root level is 0
320
        def level
321
          parent_id.nil? ? 0 : ancestors.count
322
        end
323

  
324
        # Returns a set of itself and all of its nested children
325
        def self_and_descendants
326
          nested_set_scope.scoped :conditions => [
327
            "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right
328
          ]
329
        end
330

  
331
        # Returns a set of all of its children and nested children
332
        def descendants
333
          without_self self_and_descendants
334
        end
335

  
336
        # Returns a set of only this entry's immediate children
337
        def children
338
          nested_set_scope.scoped :conditions => {parent_column_name => self}
339
        end
340

  
341
        def is_descendant_of?(other)
342
          other.left < self.left && self.left < other.right && same_scope?(other)
343
        end
344
        
345
        def is_or_is_descendant_of?(other)
346
          other.left <= self.left && self.left < other.right && same_scope?(other)
347
        end
348

  
349
        def is_ancestor_of?(other)
350
          self.left < other.left && other.left < self.right && same_scope?(other)
351
        end
352
        
353
        def is_or_is_ancestor_of?(other)
354
          self.left <= other.left && other.left < self.right && same_scope?(other)
355
        end
356
        
357
        # Check if other model is in the same scope
358
        def same_scope?(other)
359
          Array(acts_as_nested_set_options[:scope]).all? do |attr|
360
            self.send(attr) == other.send(attr)
361
          end
362
        end
363

  
364
        # Find the first sibling to the left
365
        def left_sibling
366
          siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left],
367
            :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC")
368
        end
369

  
370
        # Find the first sibling to the right
371
        def right_sibling
372
          siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left])
373
        end
374

  
375
        # Shorthand method for finding the left sibling and moving to the left of it.
376
        def move_left
377
          move_to_left_of left_sibling
378
        end
379

  
380
        # Shorthand method for finding the right sibling and moving to the right of it.
381
        def move_right
382
          move_to_right_of right_sibling
383
        end
384

  
385
        # Move the node to the left of another node (you can pass id only)
386
        def move_to_left_of(node)
387
          move_to node, :left
388
        end
389

  
390
        # Move the node to the left of another node (you can pass id only)
391
        def move_to_right_of(node)
392
          move_to node, :right
393
        end
394

  
395
        # Move the node to the child of another node (you can pass id only)
396
        def move_to_child_of(node)
397
          move_to node, :child
398
        end
399
        
400
        # Move the node to root nodes
401
        def move_to_root
402
          move_to nil, :root
403
        end
404
        
405
        def move_possible?(target)
406
          self != target && # Can't target self
407
          same_scope?(target) && # can't be in different scopes
408
          # !(left..right).include?(target.left..target.right) # this needs tested more
409
          # detect impossible move
410
          !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
411
        end
412
        
413
        def to_text
414
          self_and_descendants.map do |node|
415
            "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
416
          end.join("\n")
417
        end
418
        
419
      protected
420
      
421
        def without_self(scope)
422
          scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self]
423
        end
424
        
425
        # All nested set queries should use this nested_set_scope, which performs finds on
426
        # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
427
        # declaration.
428
        def nested_set_scope
429
          options = {:order => "#{self.class.table_name}.#{quoted_left_column_name}"}
430
          scopes = Array(acts_as_nested_set_options[:scope])
431
          options[:conditions] = scopes.inject({}) do |conditions,attr|
432
            conditions.merge attr => self[attr]
433
          end unless scopes.empty?
434
          self.class.base_class.scoped options
435
        end
436
        
437
        # on creation, set automatically lft and rgt to the end of the tree
438
        def set_default_left_and_right
439
          maxright = nested_set_scope.maximum(right_column_name) || 0
440
          # adds the new node to the right of all existing nodes
441
          self[left_column_name] = maxright + 1
442
          self[right_column_name] = maxright + 2
443
        end
444
      
445
        # Prunes a branch off of the tree, shifting all of the elements on the right
446
        # back to the left so the counts still work.
447
        def prune_from_tree
448
          return if right.nil? || left.nil? || !self.class.exists?(id)
449

  
450
          self.class.base_class.transaction do
451
            reload_nested_set
452
            if acts_as_nested_set_options[:dependent] == :destroy
453
              children.each(&:destroy)
454
            else
455
              nested_set_scope.send(:delete_all,
456
                ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
457
                  left, right]
458
              )
459
            end
460
            reload_nested_set
461
            diff = right - left + 1
462
            nested_set_scope.update_all(
463
              ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
464
              ["#{quoted_left_column_name} >= ?", right]
465
            )
466
            nested_set_scope.update_all(
467
              ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
468
              ["#{quoted_right_column_name} >= ?", right]
469
            )
470
          end
471
          
472
          # Reload is needed because children may have updated their parent (self) during deletion.
473
          reload
474
        end
475

  
476
        # reload left, right, and parent
477
        def reload_nested_set
478
          reload(:select => "#{quoted_left_column_name}, " +
479
            "#{quoted_right_column_name}, #{quoted_parent_column_name}")
480
        end
481
        
482
        def move_to(target, position)
483
          raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
484
          return if callback(:before_move) == false
485
          transaction do
486
            if target.is_a? self.class.base_class
487
              target.reload_nested_set
488
            elsif position != :root
489
              # load object if node is not an object
490
              target = nested_set_scope.find(target)
491
            end
492
            self.reload_nested_set
493
          
494
            unless position == :root || move_possible?(target)
495
              raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
496
            end
497
            
498
            bound = case position
499
              when :child;  target[right_column_name]
500
              when :left;   target[left_column_name]
501
              when :right;  target[right_column_name] + 1
502
              when :root;   1
503
              else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
504
            end
505
          
506
            if bound > self[right_column_name]
507
              bound = bound - 1
508
              other_bound = self[right_column_name] + 1
509
            else
510
              other_bound = self[left_column_name] - 1
511
            end
512

  
513
            # there would be no change
514
            return if bound == self[right_column_name] || bound == self[left_column_name]
515
          
516
            # we have defined the boundaries of two non-overlapping intervals, 
517
            # so sorting puts both the intervals and their boundaries in order
518
            a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
519

  
520
            new_parent = case position
521
              when :child;  target.id
522
              when :root;   nil
523
              else          target[parent_column_name]
524
            end
525

  
526
            self.class.base_class.update_all([
527
              "#{quoted_left_column_name} = CASE " +
528
                "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
529
                  "THEN #{quoted_left_column_name} + :d - :b " +
530
                "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
531
                  "THEN #{quoted_left_column_name} + :a - :c " +
532
                "ELSE #{quoted_left_column_name} END, " +
533
              "#{quoted_right_column_name} = CASE " +
534
                "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
535
                  "THEN #{quoted_right_column_name} + :d - :b " +
536
                "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
537
                  "THEN #{quoted_right_column_name} + :a - :c " +
538
                "ELSE #{quoted_right_column_name} END, " +
539
              "#{quoted_parent_column_name} = CASE " +
540
                "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
541
                "ELSE #{quoted_parent_column_name} END",
542
              {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
543
            ], nested_set_scope.proxy_options[:conditions])
544
          end
545
          target.reload_nested_set if target
546
          self.reload_nested_set
547
          callback(:after_move)
548
        end
549

  
550
      end
551
      
552
    end
553
  end
554
end
.svn/pristine/3e/3e497523d528dce449385801e7d571e2b8c31896.svn-base
1
bg:
2
  # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3
  direction: ltr
4
  date:
5
    formats:
6
      # Use the strftime parameters for formats.
7
      # When no format has been given, it uses default.
8
      # You can provide other formats here if you like!
9
      default: "%d-%m-%Y"
10
      short: "%b %d"
11
      long: "%B %d, %Y"
12

  
13
    day_names: [Неделя, Понеделник, Вторник, Сряда, Четвъртък, Петък, Събота]
14
    abbr_day_names: [Нед, Пон, Вто, Сря, Чет, Пет, Съб]
15

  
16
    # Don't forget the nil at the beginning; there's no such thing as a 0th month
17
    month_names: [~, Януари, Февруари, Март, Април, Май, Юни, Юли, Август, Септември, Октомври, Ноември, Декември]
18
    abbr_month_names: [~, Яну, Фев, Мар, Апр, Май, Юни, Юли, Авг, Сеп, Окт, Ное, Дек]
19
    # Used in date_select and datime_select.
20
    order:
21
      - :year
22
      - :month
23
      - :day
24

  
25
  time:
26
    formats:
27
      default: "%a, %d %b %Y %H:%M:%S %z"
28
      time: "%H:%M"
29
      short: "%d %b %H:%M"
30
      long: "%B %d, %Y %H:%M"
31
    am: "am"
32
    pm: "pm"
33

  
34
  datetime:
35
    distance_in_words:
36
      half_a_minute: "half a minute"
37
      less_than_x_seconds:
38
        one:   "по-малко от 1 секунда"
39
        other: "по-малко от %{count} секунди"
40
      x_seconds:
41
        one:   "1 секунда"
42
        other: "%{count} секунди"
43
      less_than_x_minutes:
44
        one:   "по-малко от 1 минута"
45
        other: "по-малко от %{count} минути"
46
      x_minutes:
47
        one:   "1 минута"
48
        other: "%{count} минути"
49
      about_x_hours:
50
        one:   "около 1 час"
51
        other: "около %{count} часа"
52
      x_days:
53
        one:   "1 ден"
54
        other: "%{count} дена"
55
      about_x_months:
56
        one:   "около 1 месец"
57
        other: "около %{count} месеца"
58
      x_months:
59
        one:   "1 месец"
60
        other: "%{count} месеца"
61
      about_x_years:
62
        one:   "около 1 година"
63
        other: "около %{count} години"
64
      over_x_years:
65
        one:   "над 1 година"
66
        other: "над %{count} години"
67
      almost_x_years:
68
        one:   "почти 1 година"
69
        other: "почти %{count} години"
70

  
71
  number:
72
    format:
73
      separator: "."
74
      delimiter: ""
75
      precision: 3
76

  
77
    human:
78
      format:
79
        precision: 1
80
        delimiter: ""
81
      storage_units:
82
        format: "%n %u"
83
        units:
84
          byte:
85
            one: Byte
86
            other: Bytes
87
          kb: "KB"
88
          mb: "MB"
89
          gb: "GB"
90
          tb: "TB"
91

  
92
# Used in array.to_sentence.
93
  support:
94
    array:
95
      sentence_connector: "и"
96
      skip_last_comma: false
97

  
98
  activerecord:
99
    errors:
100
      template:
101
        header:
102
          one:    "1 грешка попречи този %{model} да бъде записан"
103
          other:  "%{count} грешки попречиха този %{model} да бъде записан"
104
      messages:
105
        inclusion: "не съществува в списъка"
106
        exclusion: "е запазено"
107
        invalid: "е невалидно"
108
        confirmation: "липсва одобрение"
109
        accepted: "трябва да се приеме"
110
        empty: "не може да е празно"
111
        blank: "не може да е празно"
112
        too_long: "е прекалено дълго"
113
        too_short: "е прекалено късо"
114
        wrong_length: "е с грешна дължина"
115
        taken: "вече съществува"
116
        not_a_number: "не е число"
117
        not_a_date: "е невалидна дата"
118
        greater_than: "трябва да бъде по-голям[a/о] от %{count}"
119
        greater_than_or_equal_to: "трябва да бъде по-голям[a/о] от или равен[a/o] на %{count}"
120
        equal_to: "трябва да бъде равен[a/o] на %{count}"
121
        less_than: "трябва да бъде по-малък[a/o] от %{count}"
122
        less_than_or_equal_to: "трябва да бъде по-малък[a/o] от или равен[a/o] на %{count}"
123
        odd: "трябва да бъде нечетен[a/o]"
124
        even: "трябва да бъде четен[a/o]"
125
        greater_than_start_date: "трябва да е след началната дата"
126
        not_same_project: "не е от същия проект"
127
        circular_dependency: "Тази релация ще доведе до безкрайна зависимост"
128
        cant_link_an_issue_with_a_descendant: "Една задача не може да бъде свързвана към своя подзадача"
129

  
130
  actionview_instancetag_blank_option: Изберете
131

  
132
  general_text_No: 'Не'
133
  general_text_Yes: 'Да'
134
  general_text_no: 'не'
135
  general_text_yes: 'да'
136
  general_lang_name: 'Bulgarian (Български)'
137
  general_csv_separator: ','
138
  general_csv_decimal_separator: '.'
139
  general_csv_encoding: UTF-8
140
  general_pdf_encoding: UTF-8
141
  general_first_day_of_week: '1'
142

  
143
  notice_account_updated: Профилът е обновен успешно.
144
  notice_account_invalid_creditentials: Невалиден потребител или парола.
145
  notice_account_password_updated: Паролата е успешно променена.
146
  notice_account_wrong_password: Грешна парола
147
  notice_account_register_done: Профилът е създаден успешно.
148
  notice_account_unknown_email: Непознат e-mail.
149
  notice_can_t_change_password: Този профил е с външен метод за оторизация. Невъзможна смяна на паролата.
150
  notice_account_lost_email_sent: Изпратен ви е e-mail с инструкции за избор на нова парола.
151
  notice_account_activated: Профилът ви е активиран. Вече може да влезете в системата.
152
  notice_successful_create: Успешно създаване.
153
  notice_successful_update: Успешно обновяване.
154
  notice_successful_delete: Успешно изтриване.
155
  notice_successful_connection: Успешно свързване.
156
  notice_file_not_found: Несъществуваща или преместена страница.
157
  notice_locking_conflict: Друг потребител променя тези данни в момента.
158
  notice_not_authorized: Нямате право на достъп до тази страница.
159
  notice_not_authorized_archived_project: Проектът, който се опитвате да видите е архивиран. Ако смятате, че това не е правилно, обърнете се към администратора за разархивиране.
160
  notice_email_sent: "Изпратен e-mail на %{value}"
161
  notice_email_error: "Грешка при изпращане на e-mail (%{value})"
162
  notice_feeds_access_key_reseted: Вашия ключ за RSS достъп беше променен.
163
  notice_api_access_key_reseted: Вашият API ключ за достъп беше изчистен.
164
  notice_failed_to_save_issues: "Неуспешен запис на %{count} задачи от %{total} избрани: %{ids}."
165
  notice_failed_to_save_members: "Невъзможност за запис на член(ове): %{errors}."
166
  notice_no_issue_selected: "Няма избрани задачи."
167
  notice_account_pending: "Профилът Ви е създаден и очаква одобрение от администратор."
168
  notice_default_data_loaded: Примерната информация е заредена успешно.
169
  notice_unable_delete_version: Невъзможност за изтриване на версия
170
  notice_unable_delete_time_entry: Невъзможност за изтриване на запис на time log.
171
  notice_issue_done_ratios_updated: Обновен процент на завършените задачи.
172
  notice_gantt_chart_truncated: Мрежовият график е съкратен, понеже броят на обектите, които могат да бъдат показани е твърде голям (%{max})
173
  notice_issue_successful_create: Задача %{id} е създадена.
174

  
175
  error_can_t_load_default_data: "Грешка при зареждане на примерната информация: %{value}"
176
  error_scm_not_found: Несъществуващ обект в хранилището.
177
  error_scm_command_failed: "Грешка при опит за комуникация с хранилище: %{value}"
178
  error_scm_annotate: "Обектът не съществува или не може да бъде анотиран."
179
  error_scm_annotate_big_text_file: "Файлът не може да бъде анотиран, понеже надхвърля максималния размер за текстови файлове."
180
  error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект'
181
  error_no_tracker_in_project: Няма асоциирани тракери с този проект. Проверете настройките на проекта.
182
  error_no_default_issue_status: Няма установено подразбиращо се състояние за задачите. Моля проверете вашата конфигурация (Вижте "Администрация -> Състояния на задачи").
183
  error_can_not_delete_custom_field: Невъзможност за изтриване на потребителско поле
184
  error_can_not_delete_tracker: Този тракер съдържа задачи и не може да бъде изтрит.
185
  error_can_not_remove_role: Тази роля се използва и не може да бъде изтрита.
186
  error_can_not_reopen_issue_on_closed_version: Задача, асоциирана със затворена версия не може да бъде отворена отново
187
  error_can_not_archive_project: Този проект не може да бъде архивиран
188
  error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен.
189
  error_workflow_copy_source: Моля изберете source тракер или роля
190
  error_workflow_copy_target: Моля изберете тракер(и) и роля (роли).
191
  error_unable_delete_issue_status: Невъзможност за изтриване на състояние на задача
192
  error_unable_to_connect: Невъзможност за свързване с (%{value})
193
  error_attachment_too_big: Този файл не може да бъде качен, понеже надхвърля максималната възможна големина (%{max_size})
194
  warning_attachments_not_saved: "%{count} файла не бяха записани."
195

  
196
  mail_subject_lost_password: "Вашата парола (%{value})"
197
  mail_body_lost_password: 'За да смените паролата си, използвайте следния линк:'
198
  mail_subject_register: "Активация на профил (%{value})"
199
  mail_body_register: 'За да активирате профила си използвайте следния линк:'
200
  mail_body_account_information_external: "Можете да използвате вашия %{value} профил за вход."
201
  mail_body_account_information: Информацията за профила ви
202
  mail_subject_account_activation_request: "Заявка за активиране на профил в %{value}"
203
  mail_body_account_activation_request: "Има новорегистриран потребител (%{value}), очакващ вашето одобрение:"
204
  mail_subject_reminder: "%{count} задачи с краен срок с следващите %{days} дни"
205
  mail_body_reminder: "%{count} задачи, назначени на вас са с краен срок в следващите %{days} дни:"
206
  mail_subject_wiki_content_added: "Wiki страницата '%{id}' беше добавена"
207
  mail_body_wiki_content_added: Wiki страницата '%{id}' беше добавена от %{author}.
208
  mail_subject_wiki_content_updated: "Wiki страницата '%{id}' беше обновена"
209
  mail_body_wiki_content_updated: Wiki страницата '%{id}' беше обновена от %{author}.
210

  
211
  gui_validation_error: 1 грешка
212
  gui_validation_error_plural: "%{count} грешки"
213

  
214
  field_name: Име
215
  field_description: Описание
216
  field_summary: Анотация
217
  field_is_required: Задължително
218
  field_firstname: Име
219
  field_lastname: Фамилия
220
  field_mail: Email
221
  field_filename: Файл
222
  field_filesize: Големина
223
  field_downloads: Изтеглени файлове
224
  field_author: Автор
225
  field_created_on: От дата
226
  field_updated_on: Обновена
227
  field_field_format: Тип
228
  field_is_for_all: За всички проекти
229
  field_possible_values: Възможни стойности
230
  field_regexp: Регулярен израз
231
  field_min_length: Мин. дължина
232
  field_max_length: Макс. дължина
233
  field_value: Стойност
234
  field_category: Категория
235
  field_title: Заглавие
236
  field_project: Проект
237
  field_issue: Задача
238
  field_status: Състояние
239
  field_notes: Бележка
240
  field_is_closed: Затворена задача
241
  field_is_default: Състояние по подразбиране
242
  field_tracker: Тракер
243
  field_subject: Относно
244
  field_due_date: Крайна дата
245
  field_assigned_to: Възложена на
246
  field_priority: Приоритет
247
  field_fixed_version: Планувана версия
248
  field_user: Потребител
249
  field_principal: Principal
250
  field_role: Роля
251
  field_homepage: Начална страница
252
  field_is_public: Публичен
253
  field_parent: Подпроект на
254
  field_is_in_roadmap: Да се вижда ли в Пътна карта
255
  field_login: Потребител
256
  field_mail_notification: Известия по пощата
257
  field_admin: Администратор
258
  field_last_login_on: Последно свързване
259
  field_language: Език
260
  field_effective_date: Дата
261
  field_password: Парола
262
  field_new_password: Нова парола
263
  field_password_confirmation: Потвърждение
264
  field_version: Версия
265
  field_type: Тип
266
  field_host: Хост
267
  field_port: Порт
268
  field_account: Профил
269
  field_base_dn: Base DN
270
  field_attr_login: Атрибут Login
271
  field_attr_firstname: Атрибут Първо име (Firstname)
272
  field_attr_lastname: Атрибут Фамилия (Lastname)
273
  field_attr_mail: Атрибут Email
274
  field_onthefly: Динамично създаване на потребител
275
  field_start_date: Начална дата
276
  field_done_ratio: "% Прогрес"
277
  field_auth_source: Начин на оторизация
278
  field_hide_mail: Скрий e-mail адреса ми
279
  field_comments: Коментар
280
  field_url: Адрес
281
  field_start_page: Начална страница
282
  field_subproject: Подпроект
283
  field_hours: Часове
284
  field_activity: Дейност
285
  field_spent_on: Дата
286
  field_identifier: Идентификатор
287
  field_is_filter: Използва се за филтър
288
  field_issue_to: Свързана задача
289
  field_delay: Отместване
290
  field_assignable: Възможно е възлагане на задачи за тази роля
291
  field_redirect_existing_links: Пренасочване на съществуващи линкове
292
  field_estimated_hours: Изчислено време
293
  field_column_names: Колони
294
  field_time_entries: Log time
295
  field_time_zone: Часова зона
296
  field_searchable: С възможност за търсене
297
  field_default_value: Стойност по подразбиране
298
  field_comments_sorting: Сортиране на коментарите
299
  field_parent_title: Родителска страница
300
  field_editable: Editable
301
  field_watcher: Наблюдател
302
  field_identity_url: OpenID URL
303
  field_content: Съдържание
304
  field_group_by: Групиране на резултатите по
305
  field_sharing: Sharing
306
  field_parent_issue: Родителска задача
307
  field_member_of_group: Член на група
308
  field_assigned_to_role: Assignee's role
309
  field_text: Текстово поле
310
  field_visible: Видим
311
  field_warn_on_leaving_unsaved: Предупреди ме, когато напускам страница с незаписано съдържание
312
  field_issues_visibility: Видимост на задачите
313
  field_is_private: Лична
314
  field_commit_logs_encoding: Кодова таблица на съобщенията при поверяване
315
  field_scm_path_encoding: Кодова таблица на пътищата (path)
316
  field_path_to_repository: Път до хранилището
317
  field_root_directory: Коренна директория (папка)
318
  field_cvsroot: CVSROOT
319
  field_cvs_module: Модул
320

  
321
  setting_app_title: Заглавие
322
  setting_app_subtitle: Описание
323
  setting_welcome_text: Допълнителен текст
324
  setting_default_language: Език по подразбиране
325
  setting_login_required: Изискване за вход в системата
326
  setting_self_registration: Регистрация от потребители
327
  setting_attachment_max_size: Максимална големина на прикачен файл
328
  setting_issues_export_limit: Максимален брой задачи за експорт
329
  setting_mail_from: E-mail адрес за емисии
330
  setting_bcc_recipients: Получатели на скрито копие (bcc)
331
  setting_plain_text_mail: само чист текст (без HTML)
332
  setting_host_name: Хост
333
  setting_text_formatting: Форматиране на текста
334
  setting_wiki_compression: Компресиране на Wiki историята
335
  setting_feeds_limit: Максимален брой записи в ATOM емисии
336
  setting_default_projects_public: Новите проекти са публични по подразбиране
337
  setting_autofetch_changesets: Автоматично извличане на ревизиите
338
  setting_sys_api_enabled: Разрешаване на WS за управление
339
  setting_commit_ref_keywords: Отбелязващи ключови думи
340
  setting_commit_fix_keywords: Приключващи ключови думи
341
  setting_autologin: Автоматичен вход
342
  setting_date_format: Формат на датата
343
  setting_time_format: Формат на часа
344
  setting_cross_project_issue_relations: Релации на задачи между проекти
345
  setting_issue_list_default_columns: Показвани колони по подразбиране
346
  setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата
347
  setting_emails_header: Emails header
348
  setting_emails_footer: Подтекст за e-mail
349
  setting_protocol: Протокол
350
  setting_per_page_options: Опции за страниране
351
  setting_user_format: Потребителски формат
352
  setting_activity_days_default: Брой дни показвани на таб Дейност
353
  setting_display_subprojects_issues: Задачите от подпроектите по подразбиране се показват в главните проекти
354
  setting_enabled_scm: Разрешена SCM
355
  setting_mail_handler_body_delimiters: Отрязване на e-mail-ите след един от тези редове
356
  setting_mail_handler_api_enabled: Разрешаване на WS за входящи e-mail-и
357
  setting_mail_handler_api_key: API ключ
358
  setting_sequential_project_identifiers: Генериране на последователни проектни идентификатори
359
  setting_gravatar_enabled: Използване на портребителски икони от Gravatar
360
  setting_gravatar_default: Подразбиращо се изображение от Gravatar
361
  setting_diff_max_lines_displayed: Максимален брой показвани diff редове
362
  setting_file_max_size_displayed: Максимален размер на текстовите файлове, показвани inline
363
  setting_repository_log_display_limit: Максимален брой на показванете ревизии в лог файла
364
  setting_openid: Рарешаване на OpenID вход и регистрация
365
  setting_password_min_length: Минимална дължина на парола
366
  setting_new_project_user_role_id: Роля, давана на потребител, създаващ проекти, който не е администратор
367
  setting_default_projects_modules: Активирани модули по подразбиране за нов проект
368
  setting_issue_done_ratio: Изчисление на процента на готови задачи с
369
  setting_issue_done_ratio_issue_field: Използване на поле '% Прогрес'
370
  setting_issue_done_ratio_issue_status: Използване на състоянието на задачите
371
  setting_start_of_week: Първи ден на седмицата
372
  setting_rest_api_enabled: Разрешаване на REST web сървис
373
  setting_cache_formatted_text: Кеширане на форматираните текстове
374
  setting_default_notification_option: Подразбиращ се начин за известяване
375
  setting_commit_logtime_enabled: Разрешаване на отчитането на работното време
376
  setting_commit_logtime_activity_id: Дейност при отчитане на работното време
377
  setting_gantt_items_limit: Максимален брой обекти, които да се показват в мрежов график
378
  setting_issue_group_assignment: Разрешено назначаването на задачи на групи
379
  setting_default_issue_start_date_to_creation_date: Начална дата на новите задачи по подразбиране да бъде днешната дата
380

  
381
  permission_add_project: Създаване на проект
382
  permission_add_subprojects: Създаване на подпроекти
383
  permission_edit_project: Редактиране на проект
384
  permission_select_project_modules: Избор на проектни модули
385
  permission_manage_members: Управление на членовете (на екип)
386
  permission_manage_project_activities: Управление на дейностите на проекта
387
  permission_manage_versions: Управление на версиите
388
  permission_manage_categories: Управление на категориите
389
  permission_view_issues: Разглеждане на задачите
390
  permission_add_issues: Добавяне на задачи
391
  permission_edit_issues: Редактиране на задачи
392
  permission_manage_issue_relations: Управление на връзките между задачите
393
  permission_set_own_issues_private: Установяване на собствените задачи публични или лични
394
  permission_set_issues_private: Установяване на задачите публични или лични
395
  permission_add_issue_notes: Добавяне на бележки
396
  permission_edit_issue_notes: Редактиране на бележки
397
  permission_edit_own_issue_notes: Редактиране на собствени бележки
398
  permission_move_issues: Преместване на задачи
399
  permission_delete_issues: Изтриване на задачи
400
  permission_manage_public_queries: Управление на публичните заявки
401
  permission_save_queries: Запис на запитвания (queries)
402
  permission_view_gantt: Разглеждане на мрежов график
403
  permission_view_calendar: Разглеждане на календари
404
  permission_view_issue_watchers: Разглеждане на списък с наблюдатели
405
  permission_add_issue_watchers: Добавяне на наблюдатели
406
  permission_delete_issue_watchers: Изтриване на наблюдатели
407
  permission_log_time: Log spent time
408
  permission_view_time_entries: Разглеждане на изразходваното време
409
  permission_edit_time_entries: Редактиране на time logs
410
  permission_edit_own_time_entries: Редактиране на собствените time logs
411
  permission_manage_news: Управление на новини
412
  permission_comment_news: Коментиране на новини
413
  permission_manage_documents: Управление на документи
414
  permission_view_documents: Разглеждане на документи
415
  permission_manage_files: Управление на файлове
416
  permission_view_files: Разглеждане на файлове
417
  permission_manage_wiki: Управление на wiki
418
  permission_rename_wiki_pages: Преименуване на wiki страници
419
  permission_delete_wiki_pages: Изтриване на wiki страници
420
  permission_view_wiki_pages: Разглеждане на wiki
421
  permission_view_wiki_edits: Разглеждане на wiki история
422
  permission_edit_wiki_pages: Редактиране на wiki страници
423
  permission_delete_wiki_pages_attachments: Изтриване на прикачени файлове към wiki страници
424
  permission_protect_wiki_pages: Заключване на wiki страници
425
  permission_manage_repository: Управление на хранилища
426
  permission_browse_repository: Разглеждане на хранилища
427
  permission_view_changesets: Разглеждане на changesets
428
  permission_commit_access: Поверяване
429
  permission_manage_boards: Управление на boards
430
  permission_view_messages: Разглеждане на съобщения
431
  permission_add_messages: Публикуване на съобщения
432
  permission_edit_messages: Редактиране на съобщения
433
  permission_edit_own_messages: Редактиране на собствени съобщения
434
  permission_delete_messages: Изтриване на съобщения
435
  permission_delete_own_messages: Изтриване на собствени съобщения
436
  permission_export_wiki_pages: Експорт на wiki страници
437
  permission_manage_subtasks: Управление на подзадачите
438

  
439
  project_module_issue_tracking: Тракинг
440
  project_module_time_tracking: Отделяне на време
441
  project_module_news: Новини
442
  project_module_documents: Документи
443
  project_module_files: Файлове
444
  project_module_wiki: Wiki
445
  project_module_repository: Хранилище
446
  project_module_boards: Форуми
447
  project_module_calendar: Календар
448
  project_module_gantt: Мрежов график
449

  
450
  label_user: Потребител
451
  label_user_plural: Потребители
452
  label_user_new: Нов потребител
453
  label_user_anonymous: Анонимен
454
  label_project: Проект
455
  label_project_new: Нов проект
456
  label_project_plural: Проекти
457
  label_x_projects:
458
    zero:  0 проекта
459
    one:   1 проект
460
    other: "%{count} проекта"
461
  label_project_all: Всички проекти
462
  label_project_latest: Последни проекти
463
  label_issue: Задача
464
  label_issue_new: Нова задача
465
  label_issue_plural: Задачи
466
  label_issue_view_all: Всички задачи
467
  label_issues_by: "Задачи по %{value}"
468
  label_issue_added: Добавена задача
469
  label_issue_updated: Обновена задача
470
  label_issue_note_added: Добавена бележка
471
  label_issue_status_updated: Обновено състояние
472
  label_issue_priority_updated: Обновен приоритет
473
  label_document: Документ
474
  label_document_new: Нов документ
475
  label_document_plural: Документи
476
  label_document_added: Добавен документ
477
  label_role: Роля
478
  label_role_plural: Роли
479
  label_role_new: Нова роля
480
  label_role_and_permissions: Роли и права
481
  label_role_anonymous: Анонимен
482
  label_role_non_member: Не член
483
  label_member: Член
484
  label_member_new: Нов член
485
  label_member_plural: Членове
486
  label_tracker: Тракер
487
  label_tracker_plural: Тракери
488
  label_tracker_new: Нов тракер
489
  label_workflow: Работен процес
490
  label_issue_status: Състояние на задача
491
  label_issue_status_plural: Състояния на задачи
492
  label_issue_status_new: Ново състояние
493
  label_issue_category: Категория задача
494
  label_issue_category_plural: Категории задачи
495
  label_issue_category_new: Нова категория
496
  label_custom_field: Потребителско поле
497
  label_custom_field_plural: Потребителски полета
498
  label_custom_field_new: Ново потребителско поле
499
  label_enumerations: Списъци
500
  label_enumeration_new: Нова стойност
501
  label_information: Информация
502
  label_information_plural: Информация
503
  label_please_login: Вход
504
  label_register: Регистрация
505
  label_login_with_open_id_option: или вход чрез OpenID
506
  label_password_lost: Забравена парола
507
  label_home: Начало
508
  label_my_page: Лична страница
509
  label_my_account: Профил
510
  label_my_projects: Проекти, в които участвам
511
  label_my_page_block: Блокове в личната страница
512
  label_administration: Администрация
513
  label_login: Вход
514
  label_logout: Изход
515
  label_help: Помощ
516
  label_reported_issues: Публикувани задачи
517
  label_assigned_to_me_issues: Възложени на мен
518
  label_last_login: Последно свързване
519
  label_registered_on: Регистрация
520
  label_activity: Дейност
521
  label_overall_activity: Цялостна дейност
522
  label_user_activity: "Активност на %{value}"
523
  label_new: Нов
524
  label_logged_as: Здравейте,
525
  label_environment: Среда
526
  label_authentication: Оторизация
527
  label_auth_source: Начин на оторозация
528
  label_auth_source_new: Нов начин на оторизация
529
  label_auth_source_plural: Начини на оторизация
530
  label_subproject_plural: Подпроекти
531
  label_subproject_new: Нов подпроект
532
  label_and_its_subprojects: "%{value} и неговите подпроекти"
533
  label_min_max_length: Минимална - максимална дължина
534
  label_list: Списък
535
  label_date: Дата
536
  label_integer: Целочислен
537
  label_float: Дробно
538
  label_boolean: Чекбокс
539
  label_string: Текст
540
  label_text: Дълъг текст
541
  label_attribute: Атрибут
542
  label_attribute_plural: Атрибути
543
  label_download: "%{count} изтегляне"
544
  label_download_plural: "%{count} изтегляния"
545
  label_no_data: Няма изходни данни
546
  label_change_status: Промяна на състоянието
547
  label_history: История
548
  label_attachment: Файл
549
  label_attachment_new: Нов файл
550
  label_attachment_delete: Изтриване
551
  label_attachment_plural: Файлове
552
  label_file_added: Добавен файл
553
  label_report: Справка
554
  label_report_plural: Справки
555
  label_news: Новини
556
  label_news_new: Добави
557
  label_news_plural: Новини
558
  label_news_latest: Последни новини
559
  label_news_view_all: Виж всички
560
  label_news_added: Добавена новина
561
  label_news_comment_added: Добавен коментар към новина
562
  label_settings: Настройки
563
  label_overview: Общ изглед
564
  label_version: Версия
565
  label_version_new: Нова версия
566
  label_version_plural: Версии
567
  label_close_versions: Затваряне на завършените версии
568
  label_confirmation: Одобрение
569
  label_export_to: Експорт към
570
  label_read: Read...
571
  label_public_projects: Публични проекти
572
  label_open_issues: отворена
573
  label_open_issues_plural: отворени
574
  label_closed_issues: затворена
575
  label_closed_issues_plural: затворени
576
  label_x_open_issues_abbr_on_total:
577
    zero:  0 отворени / %{total}
578
    one:   1 отворена / %{total}
579
    other: "%{count} отворени / %{total}"
580
  label_x_open_issues_abbr:
581
    zero:  0 отворени
582
    one:   1 отворена
583
    other: "%{count} отворени"
584
  label_x_closed_issues_abbr:
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff