Revision 1298:4f746d8966dd .svn/pristine/4d

View differences:

.svn/pristine/4d/4d32c1bf128521985a7cc66ecfe319a7797f8c8c.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
class TimeEntry < ActiveRecord::Base
19
  include Redmine::SafeAttributes
20
  # could have used polymorphic association
21
  # project association here allows easy loading of time entries at project level with one database trip
22
  belongs_to :project
23
  belongs_to :issue
24
  belongs_to :user
25
  belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
26

  
27
  attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
28

  
29
  acts_as_customizable
30
  acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
31
                :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
32
                :author => :user,
33
                :group => :issue,
34
                :description => :comments
35

  
36
  acts_as_activity_provider :timestamp => "#{table_name}.created_on",
37
                            :author_key => :user_id,
38
                            :find_options => {:include => :project}
39

  
40
  validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
41
  validates_numericality_of :hours, :allow_nil => true, :message => :invalid
42
  validates_length_of :comments, :maximum => 255, :allow_nil => true
43
  validates :spent_on, :date => true
44
  before_validation :set_project_if_nil
45
  validate :validate_time_entry
46

  
47
  scope :visible, lambda {|*args|
48
    includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args))
49
  }
50
  scope :on_issue, lambda {|issue|
51
    includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
52
  }
53
  scope :on_project, lambda {|project, include_subprojects|
54
    includes(:project).where(project.project_condition(include_subprojects))
55
  }
56
  scope :spent_between, lambda {|from, to|
57
    if from && to
58
     where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to)
59
    elsif from
60
     where("#{TimeEntry.table_name}.spent_on >= ?", from)
61
    elsif to
62
     where("#{TimeEntry.table_name}.spent_on <= ?", to)
63
    else
64
     where(nil)
65
    end
66
  }
67

  
68
  safe_attributes 'hours', 'comments', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
69

  
70
  def initialize(attributes=nil, *args)
71
    super
72
    if new_record? && self.activity.nil?
73
      if default_activity = TimeEntryActivity.default
74
        self.activity_id = default_activity.id
75
      end
76
      self.hours = nil if hours == 0
77
    end
78
  end
79

  
80
  def set_project_if_nil
81
    self.project = issue.project if issue && project.nil?
82
  end
83

  
84
  def validate_time_entry
85
    errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
86
    errors.add :project_id, :invalid if project.nil?
87
    errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
88
  end
89

  
90
  def hours=(h)
91
    write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
92
  end
93

  
94
  def hours
95
    h = read_attribute(:hours)
96
    if h.is_a?(Float)
97
      h.round(2)
98
    else
99
      h
100
    end
101
  end
102

  
103
  # tyear, tmonth, tweek assigned where setting spent_on attributes
104
  # these attributes make time aggregations easier
105
  def spent_on=(date)
106
    super
107
    if spent_on.is_a?(Time)
108
      self.spent_on = spent_on.to_date
109
    end
110
    self.tyear = spent_on ? spent_on.year : nil
111
    self.tmonth = spent_on ? spent_on.month : nil
112
    self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
113
  end
114

  
115
  # Returns true if the time entry can be edited by usr, otherwise false
116
  def editable_by?(usr)
117
    (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
118
  end
119
end
.svn/pristine/4d/4d462e78d204c3aed1b100b04b6f63c31c637a6f.svn-base
1
// ** I18N
2

  
3
// Calendar FA language
4
// Author: Behrang Noroozinia, behrangn at g mail
5
// Encoding: any
6
// Distributed under the same terms as the calendar itself.
7

  
8
// For translators: please use UTF-8 if possible.  We strongly believe that
9
// Unicode is the answer to a real internationalized world.  Also please
10
// include your contact information in the header, as can be seen above.
11

  
12
// full day names
13
Calendar._DN = new Array
14
("یک‌شنبه",
15
 "دوشنبه",
16
 "سه‌شنبه",
17
 "چهارشنبه",
18
 "پنج‌شنبه",
19
 "آدینه",
20
 "شنبه",
21
 "یک‌شنبه");
22

  
23
// Please note that the following array of short day names (and the same goes
24
// for short month names, _SMN) isn't absolutely necessary.  We give it here
25
// for exemplification on how one can customize the short day names, but if
26
// they are simply the first N letters of the full name you can simply say:
27
//
28
//   Calendar._SDN_len = N; // short day name length
29
//   Calendar._SMN_len = N; // short month name length
30
//
31
// If N = 3 then this is not needed either since we assume a value of 3 if not
32
// present, to be compatible with translation files that were written before
33
// this feature.
34

  
35
// short day names
36
Calendar._SDN = new Array
37
("یک",
38
 "دو",
39
 "سه",
40
 "چهار",
41
 "پنج",
42
 "آدینه",
43
 "شنبه",
44
 "یک");
45

  
46
// First day of the week. "0" means display Sunday first, "1" means display
47
// Monday first, etc.
48
Calendar._FD = 0;
49

  
50
// full month names
51
Calendar._MN = new Array
52
("ژانویه",
53
 "فوریه",
54
 "مارس",
55
 "آوریل",
56
 "مه",
57
 "ژوئن",
58
 "ژوئیه",
59
 "اوت",
60
 "سپتامبر",
61
 "اکتبر",
62
 "نوامبر",
63
 "دسامبر");
64

  
65
// short month names
66
Calendar._SMN = new Array
67
("ژان",
68
 "فور",
69
 "مار",
70
 "آور",
71
 "مه",
72
 "ژوئن",
73
 "ژوئیه",
74
 "اوت",
75
 "سپت",
76
 "اکت",
77
 "نوا",
78
 "دسا");
79

  
80
// tooltips
81
Calendar._TT = {};
82
Calendar._TT["INFO"] = "درباره گاهشمار";
83

  
84
Calendar._TT["ABOUT"] =
85
"DHTML Date/Time Selector\n" +
86
"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
87
"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
88
"Distributed under GNU LGPL.  See http://gnu.org/licenses/lgpl.html for details." +
89
"\n\n" +
90
"Date selection:\n" +
91
"- Use the \xab, \xbb buttons to select year\n" +
92
"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
93
"- Hold mouse button on any of the above buttons for faster selection.";
94
Calendar._TT["ABOUT_TIME"] = "\n\n" +
95
"Time selection:\n" +
96
"- Click on any of the time parts to increase it\n" +
97
"- or Shift-click to decrease it\n" +
98
"- or click and drag for faster selection.";
99

  
100
Calendar._TT["PREV_YEAR"] = "سال پیشین (برای فهرست نگه دارید)";
101
Calendar._TT["PREV_MONTH"] = "ماه پیشین ( برای فهرست نگه دارید)";
102
Calendar._TT["GO_TODAY"] = "برو به امروز";
103
Calendar._TT["NEXT_MONTH"] = "ماه پسین (برای فهرست نگه دارید)";
104
Calendar._TT["NEXT_YEAR"] = "سال پسین (برای فهرست نگه دارید)";
105
Calendar._TT["SEL_DATE"] = "گزینش";
106
Calendar._TT["DRAG_TO_MOVE"] = "برای جابجایی بکشید";
107
Calendar._TT["PART_TODAY"] = " (امروز)";
108

  
109
// the following is to inform that "%s" is to be the first day of week
110
// %s will be replaced with the day name.
111
Calendar._TT["DAY_FIRST"] = "آغاز هفته از %s";
112

  
113
// This may be locale-dependent.  It specifies the week-end days, as an array
114
// of comma-separated numbers.  The numbers are from 0 to 6: 0 means Sunday, 1
115
// means Monday, etc.
116
Calendar._TT["WEEKEND"] = "4,5";
117

  
118
Calendar._TT["CLOSE"] = "بسته";
119
Calendar._TT["TODAY"] = "امروز";
120
Calendar._TT["TIME_PART"] = "زدن (با Shift) یا کشیدن برای ویرایش";
121

  
122
// date formats
123
Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
124
Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
125

  
126
Calendar._TT["WK"] = "هفته";
127
Calendar._TT["TIME"] = "زمان:";
.svn/pristine/4d/4d4d388d3654f2fbefb4fdd7c3e99a9ffa1a3eeb.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
class GanttsController < ApplicationController
19
  menu_item :gantt
20
  before_filter :find_optional_project
21

  
22
  rescue_from Query::StatementInvalid, :with => :query_statement_invalid
23

  
24
  helper :gantt
25
  helper :issues
26
  helper :projects
27
  helper :queries
28
  include QueriesHelper
29
  helper :sort
30
  include SortHelper
31
  include Redmine::Export::PDF
32

  
33
  def show
34
    @gantt = Redmine::Helpers::Gantt.new(params)
35
    @gantt.project = @project
36
    retrieve_query
37
    @query.group_by = nil
38
    @gantt.query = @query if @query.valid?
39

  
40
    basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
41

  
42
    respond_to do |format|
43
      format.html { render :action => "show", :layout => !request.xhr? }
44
      format.png  { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
45
      format.pdf  { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
46
    end
47
  end
48
end
.svn/pristine/4d/4d670f96a6b54a9bfc31fb5cbe3121575a7b05e8.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
begin
20
  require 'mocha'
21

  
22
  class MercurialAdapterTest < ActiveSupport::TestCase
23
    HELPERS_DIR        = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR
24
    TEMPLATE_NAME      = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
25
    TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
26

  
27
    REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
28
    CHAR_1_HEX = "\xc3\x9c"
29

  
30
    if File.directory?(REPOSITORY_PATH)
31
      def setup
32
        adapter_class = Redmine::Scm::Adapters::MercurialAdapter
33
        assert adapter_class
34
        assert adapter_class.client_command
35
        assert_equal true, adapter_class.client_available
36
        assert_equal true, adapter_class.client_version_above?([0, 9, 5])
37

  
38
        @adapter = Redmine::Scm::Adapters::MercurialAdapter.new(
39
                              REPOSITORY_PATH,
40
                              nil,
41
                              nil,
42
                              nil,
43
                             'ISO-8859-1')
44
        @diff_c_support = true
45
        @char_1        = CHAR_1_HEX.dup
46
        @tag_char_1    = "tag-#{CHAR_1_HEX}-00"
47
        @branch_char_0 = "branch-#{CHAR_1_HEX}-00"
48
        @branch_char_1 = "branch-#{CHAR_1_HEX}-01"
49
        if @tag_char_1.respond_to?(:force_encoding)
50
          @char_1.force_encoding('UTF-8')
51
          @tag_char_1.force_encoding('UTF-8')
52
          @branch_char_0.force_encoding('UTF-8')
53
          @branch_char_1.force_encoding('UTF-8')
54
        end
55
      end
56

  
57
      def test_hgversion
58
        to_test = { "Mercurial Distributed SCM (version 0.9.5)\n"  => [0,9,5],
59
                    "Mercurial Distributed SCM (1.0)\n"            => [1,0],
60
                    "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
61
                    "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
62
                    "Mercurial Distributed SCM (1916e629a29d)\n"   => nil,
63
                    "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
64
                    "(1.6)\n(1.7)\n(1.8)"                          => [1,6],
65
                    "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)"                => [1,7,1]}
66

  
67
        to_test.each do |s, v|
68
          test_hgversion_for(s, v)
69
        end
70
      end
71

  
72
      def test_template_path
73
        to_test = {
74
                    [1,2]    => "1.0",
75
                    []       => "1.0",
76
                    [1,2,1]  => "1.0",
77
                    [1,7]    => "1.0",
78
                    [1,7,1]  => "1.0",
79
                    [2,0]    => "1.0",
80
                   }
81
        to_test.each do |v, template|
82
          test_template_path_for(v, template)
83
        end
84
      end
85

  
86
      def test_info
87
        [REPOSITORY_PATH, REPOSITORY_PATH + "/",
88
             REPOSITORY_PATH + "//"].each do |repo|
89
          adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo)
90
          repo_path =  adp.info.root_url.gsub(/\\/, "/")
91
          assert_equal REPOSITORY_PATH, repo_path
92
          assert_equal '31', adp.info.lastrev.revision
93
          assert_equal '31eeee7395c8',adp.info.lastrev.scmid
94
        end
95
      end
96

  
97
      def test_revisions
98
        revisions = @adapter.revisions(nil, 2, 4)
99
        assert_equal 3, revisions.size
100
        assert_equal '2', revisions[0].revision
101
        assert_equal '400bb8672109', revisions[0].scmid
102
        assert_equal '4', revisions[2].revision
103
        assert_equal 'def6d2f1254a', revisions[2].scmid
104

  
105
        revisions = @adapter.revisions(nil, 2, 4, {:limit => 2})
106
        assert_equal 2, revisions.size
107
        assert_equal '2', revisions[0].revision
108
        assert_equal '400bb8672109', revisions[0].scmid
109
      end
110

  
111
      def test_parents
112
        revs1 = @adapter.revisions(nil, 0, 0)
113
        assert_equal 1, revs1.size
114
        assert_equal [], revs1[0].parents
115
        revs2 = @adapter.revisions(nil, 1, 1)
116
        assert_equal 1, revs2.size
117
        assert_equal 1, revs2[0].parents.size
118
        assert_equal "0885933ad4f6", revs2[0].parents[0]
119
        revs3 = @adapter.revisions(nil, 30, 30)
120
        assert_equal 1, revs3.size
121
        assert_equal 2, revs3[0].parents.size
122
        assert_equal "a94b0528f24f", revs3[0].parents[0]
123
        assert_equal "3a330eb32958", revs3[0].parents[1]
124
      end
125

  
126
      def test_diff
127
        if @adapter.class.client_version_above?([1, 2])
128
          assert_nil @adapter.diff(nil, '100000')
129
        end
130
        assert_nil @adapter.diff(nil, '100000', '200000')
131
        [2, '400bb8672109', '400', 400].each do |r1|
132
          diff1 = @adapter.diff(nil, r1)
133
          if @diff_c_support
134
            assert_equal 28, diff1.size
135
            buf = diff1[24].gsub(/\r\n|\r|\n/, "")
136
            assert_equal "+    return true unless klass.respond_to?('watched_by')", buf
137
          else
138
            assert_equal 0, diff1.size
139
          end
140
          [4, 'def6d2f1254a'].each do |r2|
141
            diff2 = @adapter.diff(nil, r1, r2)
142
            assert_equal 49, diff2.size
143
            buf =  diff2[41].gsub(/\r\n|\r|\n/, "")
144
            assert_equal "+class WelcomeController < ApplicationController", buf
145
            diff3 = @adapter.diff('sources/watchers_controller.rb', r1, r2)
146
            assert_equal 20, diff3.size
147
            buf =  diff3[12].gsub(/\r\n|\r|\n/, "")
148
            assert_equal "+    @watched.remove_watcher(user)", buf
149

  
150
            diff4 = @adapter.diff(nil, r2, r1)
151
            assert_equal 49, diff4.size
152
            buf =  diff4[41].gsub(/\r\n|\r|\n/, "")
153
            assert_equal "-class WelcomeController < ApplicationController", buf
154
            diff5 = @adapter.diff('sources/watchers_controller.rb', r2, r1)
155
            assert_equal 20, diff5.size
156
            buf =  diff5[9].gsub(/\r\n|\r|\n/, "")
157
            assert_equal "-    @watched.remove_watcher(user)", buf
158
          end
159
        end
160
      end
161

  
162
      def test_diff_made_by_revision
163
        if @diff_c_support
164
          [24, '24', '4cddb4e45f52'].each do |r1|
165
            diff1 = @adapter.diff(nil, r1)
166
            assert_equal 5, diff1.size
167
            buf = diff1[4].gsub(/\r\n|\r|\n/, "")
168
            assert_equal '+0885933ad4f68d77c2649cd11f8311276e7ef7ce tag-init-revision', buf
169
          end
170
        end
171
      end
172

  
173
      def test_cat
174
        [2, '400bb8672109', '400', 400].each do |r|
175
          buf = @adapter.cat('sources/welcome_controller.rb', r)
176
          assert buf
177
          lines = buf.split("\r\n")
178
          assert_equal 25, lines.length
179
          assert_equal 'class WelcomeController < ApplicationController', lines[17]
180
        end
181
        assert_nil @adapter.cat('sources/welcome_controller.rb')
182
      end
183

  
184
      def test_annotate
185
        assert_equal [], @adapter.annotate("sources/welcome_controller.rb").lines
186
        [2, '400bb8672109', '400', 400].each do |r|
187
          ann = @adapter.annotate('sources/welcome_controller.rb', r)
188
          assert ann
189
          assert_equal '1', ann.revisions[17].revision
190
          assert_equal '9d5b5b004199', ann.revisions[17].identifier
191
          assert_equal 'jsmith', ann.revisions[0].author
192
          assert_equal 25, ann.lines.length
193
          assert_equal 'class WelcomeController < ApplicationController', ann.lines[17]
194
        end
195
      end
196

  
197
      def test_entries
198
        assert_nil @adapter.entries(nil, '100000')
199

  
200
        assert_equal 1, @adapter.entries("sources", 3).size
201
        assert_equal 1, @adapter.entries("sources", 'b3a615152df8').size
202

  
203
        [2, '400bb8672109', '400', 400].each do |r|
204
          entries1 = @adapter.entries(nil, r)
205
          assert entries1
206
          assert_equal 3, entries1.size
207
          assert_equal 'sources', entries1[1].name
208
          assert_equal 'sources', entries1[1].path
209
          assert_equal 'dir', entries1[1].kind
210
          readme = entries1[2]
211
          assert_equal 'README', readme.name
212
          assert_equal 'README', readme.path
213
          assert_equal 'file', readme.kind
214
          assert_equal 27, readme.size
215
          assert_equal '1', readme.lastrev.revision
216
          assert_equal '9d5b5b004199', readme.lastrev.identifier
217
          # 2007-12-14 10:24:01 +0100
218
          assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time
219

  
220
          entries2 = @adapter.entries('sources', r)
221
          assert entries2
222
          assert_equal 2, entries2.size
223
          assert_equal 'watchers_controller.rb', entries2[0].name
224
          assert_equal 'sources/watchers_controller.rb', entries2[0].path
225
          assert_equal 'file', entries2[0].kind
226
          assert_equal 'welcome_controller.rb', entries2[1].name
227
          assert_equal 'sources/welcome_controller.rb', entries2[1].path
228
          assert_equal 'file', entries2[1].kind
229
        end
230
      end
231

  
232
      def test_entries_tag
233
        entries1 = @adapter.entries(nil, 'tag_test.00')
234
        assert entries1
235
        assert_equal 3, entries1.size
236
        assert_equal 'sources', entries1[1].name
237
        assert_equal 'sources', entries1[1].path
238
        assert_equal 'dir', entries1[1].kind
239
        readme = entries1[2]
240
        assert_equal 'README', readme.name
241
        assert_equal 'README', readme.path
242
        assert_equal 'file', readme.kind
243
        assert_equal 21, readme.size
244
        assert_equal '0', readme.lastrev.revision
245
        assert_equal '0885933ad4f6', readme.lastrev.identifier
246
        # 2007-12-14 10:22:52 +0100
247
        assert_equal Time.gm(2007, 12, 14, 9, 22, 52), readme.lastrev.time
248
      end
249

  
250
      def test_entries_branch
251
        entries1 = @adapter.entries(nil, 'test-branch-00')
252
        assert entries1
253
        assert_equal 5, entries1.size
254
        assert_equal 'sql_escape', entries1[2].name
255
        assert_equal 'sql_escape', entries1[2].path
256
        assert_equal 'dir', entries1[2].kind
257
        readme = entries1[4]
258
        assert_equal 'README', readme.name
259
        assert_equal 'README', readme.path
260
        assert_equal 'file', readme.kind
261
        assert_equal 365, readme.size
262
        assert_equal '8', readme.lastrev.revision
263
        assert_equal 'c51f5bb613cd', readme.lastrev.identifier
264
        # 2001-02-01 00:00:00 -0900
265
        assert_equal Time.gm(2001, 2, 1, 9, 0, 0), readme.lastrev.time
266
      end
267

  
268
      def test_locate_on_outdated_repository
269
        assert_equal 1, @adapter.entries("images", 0).size
270
        assert_equal 2, @adapter.entries("images").size
271
        assert_equal 2, @adapter.entries("images", 2).size
272
      end
273

  
274
      def test_access_by_nodeid
275
        path = 'sources/welcome_controller.rb'
276
        assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400bb8672109')
277
      end
278

  
279
      def test_access_by_fuzzy_nodeid
280
        path = 'sources/welcome_controller.rb'
281
        # falls back to nodeid
282
        assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400')
283
      end
284

  
285
      def test_tags
286
        assert_equal [@tag_char_1, 'tag_test.00', 'tag-init-revision'], @adapter.tags
287
      end
288

  
289
      def test_tagmap
290
        tm = {
291
          @tag_char_1         => 'adf805632193',
292
          'tag_test.00'       => '6987191f453a',
293
          'tag-init-revision' => '0885933ad4f6',
294
          }
295
        assert_equal tm, @adapter.tagmap
296
      end
297

  
298
      def test_branches
299
        brs = []
300
        @adapter.branches.each do |b|
301
          brs << b
302
        end
303
        assert_equal 7, brs.length
304
        assert_equal 'default', brs[0].to_s
305
        assert_equal '31', brs[0].revision
306
        assert_equal '31eeee7395c8', brs[0].scmid
307
        assert_equal 'test-branch-01', brs[1].to_s
308
        assert_equal '30', brs[1].revision
309
        assert_equal 'ad4dc4f80284', brs[1].scmid
310
        assert_equal @branch_char_1, brs[2].to_s
311
        assert_equal '27', brs[2].revision
312
        assert_equal '7bbf4c738e71', brs[2].scmid
313
        assert_equal 'branch (1)[2]&,%.-3_4', brs[3].to_s
314
        assert_equal '25', brs[3].revision
315
        assert_equal 'afc61e85bde7', brs[3].scmid
316
        assert_equal @branch_char_0, brs[4].to_s
317
        assert_equal '23', brs[4].revision
318
        assert_equal 'c8d3e4887474', brs[4].scmid
319
        assert_equal 'test_branch.latin-1', brs[5].to_s
320
        assert_equal '22', brs[5].revision
321
        assert_equal 'c2ffe7da686a', brs[5].scmid
322
        assert_equal 'test-branch-00', brs[6].to_s
323
        assert_equal '13', brs[6].revision
324
        assert_equal '3a330eb32958', brs[6].scmid
325
      end
326

  
327
      def test_branchmap
328
        bm = {
329
           'default'               => '31eeee7395c8',
330
           'test_branch.latin-1'   => 'c2ffe7da686a',
331
           'branch (1)[2]&,%.-3_4' => 'afc61e85bde7',
332
           'test-branch-00'        => '3a330eb32958',
333
           "test-branch-01"        => 'ad4dc4f80284',
334
           @branch_char_0          => 'c8d3e4887474',
335
           @branch_char_1          => '7bbf4c738e71',
336
         }
337
        assert_equal bm, @adapter.branchmap
338
      end
339

  
340
      def test_path_space
341
        p = 'README (1)[2]&,%.-3_4'
342
        [15, '933ca60293d7'].each do |r1|
343
          assert @adapter.diff(p, r1)
344
          assert @adapter.cat(p, r1)
345
          assert_equal 1, @adapter.annotate(p, r1).lines.length
346
          [25, 'afc61e85bde7'].each do |r2|
347
            assert @adapter.diff(p, r1, r2)
348
          end
349
        end
350
      end
351

  
352
      def test_tag_non_ascii
353
        p = "latin-1-dir/test-#{@char_1}-1.txt"
354
        assert @adapter.cat(p, @tag_char_1)
355
        assert_equal 1, @adapter.annotate(p, @tag_char_1).lines.length
356
      end
357

  
358
      def test_branch_non_ascii
359
        p = "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-1.txt"
360
        assert @adapter.cat(p, @branch_char_1)
361
        assert_equal 1, @adapter.annotate(p, @branch_char_1).lines.length
362
      end
363

  
364
      def test_nodes_in_branch
365
         [
366
            'default',
367
            @branch_char_1,
368
            'branch (1)[2]&,%.-3_4',
369
            @branch_char_0,
370
            'test_branch.latin-1',
371
            'test-branch-00',
372
               ].each do |bra|
373
          nib0 = @adapter.nodes_in_branch(bra)
374
          assert nib0
375
          nib1 = @adapter.nodes_in_branch(bra, :limit => 1)
376
          assert_equal 1, nib1.size
377
          case bra
378
            when 'branch (1)[2]&,%.-3_4'
379
              if @adapter.class.client_version_above?([1, 6])
380
                assert_equal 3, nib0.size
381
                assert_equal nib0[0], 'afc61e85bde7'
382
                nib2 = @adapter.nodes_in_branch(bra, :limit => 2)
383
                assert_equal 2, nib2.size
384
                assert_equal nib2[1], '933ca60293d7'
385
              end
386
            when @branch_char_1
387
              if @adapter.class.client_version_above?([1, 6])
388
                assert_equal 2, nib0.size
389
                assert_equal nib0[1], '08ff3227303e'
390
                nib2 = @adapter.nodes_in_branch(bra, :limit => 1)
391
                assert_equal 1, nib2.size
392
                assert_equal nib2[0], '7bbf4c738e71'
393
              end
394
          end
395
        end
396
      end
397

  
398
      def test_path_encoding_default_utf8
399
        adpt1 = Redmine::Scm::Adapters::MercurialAdapter.new(
400
                                  REPOSITORY_PATH
401
                                )
402
        assert_equal "UTF-8", adpt1.path_encoding
403
        adpt2 = Redmine::Scm::Adapters::MercurialAdapter.new(
404
                                  REPOSITORY_PATH,
405
                                  nil,
406
                                  nil,
407
                                  nil,
408
                                  ""
409
                                )
410
        assert_equal "UTF-8", adpt2.path_encoding
411
      end
412

  
413
      private
414

  
415
      def test_hgversion_for(hgversion, version)
416
        @adapter.class.expects(:hgversion_from_command_line).returns(hgversion)
417
        assert_equal version, @adapter.class.hgversion
418
      end
419

  
420
      def test_template_path_for(version, template)
421
        assert_equal "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}",
422
                     @adapter.class.template_path_for(version)
423
        assert File.exist?(@adapter.class.template_path_for(version))
424
      end
425
    else
426
      puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!"
427
      def test_fake; assert true end
428
    end
429
  end
430
rescue LoadError
431
  class MercurialMochaFake < ActiveSupport::TestCase
432
    def test_fake; assert(false, "Requires mocha to run those tests")  end
433
  end
434
end
.svn/pristine/4d/4db1f392c52d1c52ef2d37c9a27b017bd4d891ac.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2011  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
  class PluginNotFound < StandardError; end
21
  class PluginRequirementError < StandardError; end
22

  
23
  # Base class for Redmine plugins.
24
  # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
25
  #
26
  #   Redmine::Plugin.register :example do
27
  #     name 'Example plugin'
28
  #     author 'John Smith'
29
  #     description 'This is an example plugin for Redmine'
30
  #     version '0.0.1'
31
  #     settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
32
  #   end
33
  #
34
  # === Plugin attributes
35
  #
36
  # +settings+ is an optional attribute that let the plugin be configurable.
37
  # It must be a hash with the following keys:
38
  # * <tt>:default</tt>: default value for the plugin settings
39
  # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
40
  # Example:
41
  #   settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
42
  # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
43
  #
44
  # When rendered, the plugin settings value is available as the local variable +settings+
45
  class Plugin
46
    @registered_plugins = {}
47
    class << self
48
      attr_reader :registered_plugins
49
      private :new
50

  
51
      def def_field(*names)
52
        class_eval do
53
          names.each do |name|
54
            define_method(name) do |*args|
55
              args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
56
            end
57
          end
58
        end
59
      end
60
    end
61
    def_field :name, :description, :url, :author, :author_url, :version, :settings
62
    attr_reader :id
63

  
64
    # Plugin constructor
65
    def self.register(id, &block)
66
      p = new(id)
67
      p.instance_eval(&block)
68
      # Set a default name if it was not provided during registration
69
      p.name(id.to_s.humanize) if p.name.nil?
70
      # Adds plugin locales if any
71
      # YAML translation files should be found under <plugin>/config/locales/
72
      ::I18n.load_path += Dir.glob(File.join(Rails.root, 'vendor', 'plugins', id.to_s, 'config', 'locales', '*.yml'))
73
      registered_plugins[id] = p
74
    end
75

  
76
    # Returns an array of all registered plugins
77
    def self.all
78
      registered_plugins.values.sort
79
    end
80

  
81
    # Finds a plugin by its id
82
    # Returns a PluginNotFound exception if the plugin doesn't exist
83
    def self.find(id)
84
      registered_plugins[id.to_sym] || raise(PluginNotFound)
85
    end
86

  
87
    # Clears the registered plugins hash
88
    # It doesn't unload installed plugins
89
    def self.clear
90
      @registered_plugins = {}
91
    end
92

  
93
    # Checks if a plugin is installed
94
    #
95
    # @param [String] id name of the plugin
96
    def self.installed?(id)
97
      registered_plugins[id.to_sym].present?
98
    end
99

  
100
    def initialize(id)
101
      @id = id.to_sym
102
    end
103

  
104
    def <=>(plugin)
105
      self.id.to_s <=> plugin.id.to_s
106
    end
107

  
108
    # Sets a requirement on Redmine version
109
    # Raises a PluginRequirementError exception if the requirement is not met
110
    #
111
    # Examples
112
    #   # Requires Redmine 0.7.3 or higher
113
    #   requires_redmine :version_or_higher => '0.7.3'
114
    #   requires_redmine '0.7.3'
115
    #
116
    #   # Requires a specific Redmine version
117
    #   requires_redmine :version => '0.7.3'              # 0.7.3 only
118
    #   requires_redmine :version => ['0.7.3', '0.8.0']   # 0.7.3 or 0.8.0
119
    def requires_redmine(arg)
120
      arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
121
      arg.assert_valid_keys(:version, :version_or_higher)
122

  
123
      current = Redmine::VERSION.to_a
124
      arg.each do |k, v|
125
        v = [] << v unless v.is_a?(Array)
126
        versions = v.collect {|s| s.split('.').collect(&:to_i)}
127
        case k
128
        when :version_or_higher
129
          raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
130
          unless (current <=> versions.first) >= 0
131
            raise PluginRequirementError.new("#{id} plugin requires Redmine #{v} or higher but current is #{current.join('.')}")
132
          end
133
        when :version
134
          unless versions.include?(current.slice(0,3))
135
            raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{v.join(', ')} but current is #{current.join('.')}")
136
          end
137
        end
138
      end
139
      true
140
    end
141

  
142
    # Sets a requirement on a Redmine plugin version
143
    # Raises a PluginRequirementError exception if the requirement is not met
144
    #
145
    # Examples
146
    #   # Requires a plugin named :foo version 0.7.3 or higher
147
    #   requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
148
    #   requires_redmine_plugin :foo, '0.7.3'
149
    #
150
    #   # Requires a specific version of a Redmine plugin
151
    #   requires_redmine_plugin :foo, :version => '0.7.3'              # 0.7.3 only
152
    #   requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0']   # 0.7.3 or 0.8.0
153
    def requires_redmine_plugin(plugin_name, arg)
154
      arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
155
      arg.assert_valid_keys(:version, :version_or_higher)
156

  
157
      plugin = Plugin.find(plugin_name)
158
      current = plugin.version.split('.').collect(&:to_i)
159

  
160
      arg.each do |k, v|
161
        v = [] << v unless v.is_a?(Array)
162
        versions = v.collect {|s| s.split('.').collect(&:to_i)}
163
        case k
164
        when :version_or_higher
165
          raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
166
          unless (current <=> versions.first) >= 0
167
            raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
168
          end
169
        when :version
170
          unless versions.include?(current.slice(0,3))
171
            raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
172
          end
173
        end
174
      end
175
      true
176
    end
177

  
178
    # Adds an item to the given +menu+.
179
    # The +id+ parameter (equals to the project id) is automatically added to the url.
180
    #   menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
181
    #
182
    # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
183
    #
184
    def menu(menu, item, url, options={})
185
      Redmine::MenuManager.map(menu).push(item, url, options)
186
    end
187
    alias :add_menu_item :menu
188

  
189
    # Removes +item+ from the given +menu+.
190
    def delete_menu_item(menu, item)
191
      Redmine::MenuManager.map(menu).delete(item)
192
    end
193

  
194
    # Defines a permission called +name+ for the given +actions+.
195
    #
196
    # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
197
    #   permission :destroy_contacts, { :contacts => :destroy }
198
    #   permission :view_contacts, { :contacts => [:index, :show] }
199
    #
200
    # The +options+ argument can be used to make the permission public (implicitly given to any user)
201
    # or to restrict users the permission can be given to.
202
    #
203
    # Examples
204
    #   # A permission that is implicitly given to any user
205
    #   # This permission won't appear on the Roles & Permissions setup screen
206
    #   permission :say_hello, { :example => :say_hello }, :public => true
207
    #
208
    #   # A permission that can be given to any user
209
    #   permission :say_hello, { :example => :say_hello }
210
    #
211
    #   # A permission that can be given to registered users only
212
    #   permission :say_hello, { :example => :say_hello }, :require => :loggedin
213
    #
214
    #   # A permission that can be given to project members only
215
    #   permission :say_hello, { :example => :say_hello }, :require => :member
216
    def permission(name, actions, options = {})
217
      if @project_module
218
        Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
219
      else
220
        Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
221
      end
222
    end
223

  
224
    # Defines a project module, that can be enabled/disabled for each project.
225
    # Permissions defined inside +block+ will be bind to the module.
226
    #
227
    #   project_module :things do
228
    #     permission :view_contacts, { :contacts => [:list, :show] }, :public => true
229
    #     permission :destroy_contacts, { :contacts => :destroy }
230
    #   end
231
    def project_module(name, &block)
232
      @project_module = name
233
      self.instance_eval(&block)
234
      @project_module = nil
235
    end
236

  
237
    # Registers an activity provider.
238
    #
239
    # Options:
240
    # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
241
    # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
242
    #
243
    # A model can provide several activity event types.
244
    #
245
    # Examples:
246
    #   register :news
247
    #   register :scrums, :class_name => 'Meeting'
248
    #   register :issues, :class_name => ['Issue', 'Journal']
249
    #
250
    # Retrieving events:
251
    # Associated model(s) must implement the find_events class method.
252
    # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
253
    #
254
    # The following call should return all the scrum events visible by current user that occured in the 5 last days:
255
    #   Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
256
    #   Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
257
    #
258
    # Note that :view_scrums permission is required to view these events in the activity view.
259
    def activity_provider(*args)
260
      Redmine::Activity.register(*args)
261
    end
262

  
263
    # Registers a wiki formatter.
264
    #
265
    # Parameters:
266
    # * +name+ - human-readable name
267
    # * +formatter+ - formatter class, which should have an instance method +to_html+
268
    # * +helper+ - helper module, which will be included by wiki pages
269
    def wiki_format_provider(name, formatter, helper)
270
      Redmine::WikiFormatting.register(name, formatter, helper)
271
    end
272

  
273
    # Returns +true+ if the plugin can be configured.
274
    def configurable?
275
      settings && settings.is_a?(Hash) && !settings[:partial].blank?
276
    end
277
  end
278
end
.svn/pristine/4d/4db92861e8516d6d862c43d4820c7fc671e920ed.svn-base
1
# Finnish translations for Ruby on Rails
2
# by Marko Seppä (marko.seppa@gmail.com)
3

  
4
fi:
5
  direction: ltr
6
  date:
7
    formats:
8
      default: "%e. %Bta %Y"
9
      long: "%A%e. %Bta %Y"
10
      short: "%e.%m.%Y"
11

  
12
    day_names: [Sunnuntai, Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai]
13
    abbr_day_names: [Su, Ma, Ti, Ke, To, Pe, La]
14
    month_names: [~, Tammikuu, Helmikuu, Maaliskuu, Huhtikuu, Toukokuu, Kesäkuu, Heinäkuu, Elokuu, Syyskuu, Lokakuu, Marraskuu, Joulukuu]
15
    abbr_month_names: [~, Tammi, Helmi, Maalis, Huhti, Touko, Kesä, Heinä, Elo, Syys, Loka, Marras, Joulu]
16
    order:
17
      - :day
18
      - :month
19
      - :year
20

  
21
  time:
22
    formats:
23
      default: "%a, %e. %b %Y %H:%M:%S %z"
24
      time: "%H:%M"
25
      short: "%e. %b %H:%M"
26
      long: "%B %d, %Y %H:%M"
27
    am: "aamupäivä"
28
    pm: "iltapäivä"
29

  
30
  support:
31
    array:
32
      words_connector: ", "
33
      two_words_connector: " ja "
34
      last_word_connector: " ja "
35

  
36

  
37

  
38
  number:
39
    format:
40
      separator: ","
41
      delimiter: "."
42
      precision: 3
43

  
44
    currency:
45
      format:
46
        format: "%n %u"
47
        unit: "€"
48
        separator: ","
49
        delimiter: "."
50
        precision: 2
51

  
52
    percentage:
53
      format:
54
        # separator:
55
        delimiter: ""
56
        # precision:
57

  
58
    precision:
59
      format:
60
        # separator:
61
        delimiter: ""
62
        # precision:
63

  
64
    human:
65
      format:
66
        delimiter: ""
67
        precision: 3
68
      storage_units:
69
        format: "%n %u"
70
        units:
71
          byte:
72
            one: "Tavua"
73
            other: "Tavua"
74
          kb: "KB"
75
          mb: "MB"
76
          gb: "GB"
77
          tb: "TB"
78

  
79
  datetime:
80
    distance_in_words:
81
      half_a_minute: "puoli minuuttia"
82
      less_than_x_seconds:
83
        one:   "aiemmin kuin sekunti"
84
        other: "aiemmin kuin %{count} sekuntia"
85
      x_seconds:
86
        one:   "sekunti"
87
        other: "%{count} sekuntia"
88
      less_than_x_minutes:
89
        one:   "aiemmin kuin minuutti"
90
        other: "aiemmin kuin %{count} minuuttia"
91
      x_minutes:
92
        one:   "minuutti"
93
        other: "%{count} minuuttia"
94
      about_x_hours:
95
        one:   "noin tunti"
96
        other: "noin %{count} tuntia"
97
      x_hours:
98
        one:   "1 tunti"
99
        other: "%{count} tuntia"
100
      x_days:
101
        one:   "päivä"
102
        other: "%{count} päivää"
103
      about_x_months:
104
        one:   "noin kuukausi"
105
        other: "noin %{count} kuukautta"
106
      x_months:
107
        one:   "kuukausi"
108
        other: "%{count} kuukautta"
109
      about_x_years:
110
        one:   "vuosi"
111
        other: "noin %{count} vuotta"
112
      over_x_years:
113
        one:   "yli vuosi"
114
        other: "yli %{count} vuotta"
115
      almost_x_years:
116
        one:   "almost 1 year"
117
        other: "almost %{count} years"
118
    prompts:
119
      year:   "Vuosi"
120
      month:  "Kuukausi"
121
      day:    "Päivä"
122
      hour:   "Tunti"
123
      minute: "Minuutti"
124
      second: "Sekuntia"
125

  
126
  activerecord:
127
    errors:
128
      template:
129
        header:
130
          one:    "1 virhe esti tämän %{model} mallinteen tallentamisen"
131
          other:  "%{count} virhettä esti tämän %{model} mallinteen tallentamisen"
132
        body: "Seuraavat kentät aiheuttivat ongelmia:"
133
      messages:
134
        inclusion: "ei löydy listauksesta"
135
        exclusion: "on jo varattu"
136
        invalid: "on kelvoton"
137
        confirmation: "ei vastaa varmennusta"
138
        accepted: "täytyy olla hyväksytty"
139
        empty: "ei voi olla tyhjä"
140
        blank: "ei voi olla sisällötön"
141
        too_long: "on liian pitkä (maksimi on %{count} merkkiä)"
142
        too_short: "on liian lyhyt (minimi on %{count} merkkiä)"
143
        wrong_length: "on väärän pituinen (täytyy olla täsmälleen %{count} merkkiä)"
144
        taken: "on jo käytössä"
145
        not_a_number: "ei ole numero"
146
        greater_than: "täytyy olla suurempi kuin %{count}"
147
        greater_than_or_equal_to: "täytyy olla suurempi tai yhtä suuri kuin%{count}"
148
        equal_to: "täytyy olla yhtä suuri kuin %{count}"
149
        less_than: "täytyy olla pienempi kuin %{count}"
150
        less_than_or_equal_to: "täytyy olla pienempi tai yhtä suuri kuin %{count}"
151
        odd: "täytyy olla pariton"
152
        even: "täytyy olla parillinen"
153
        greater_than_start_date: "tulee olla aloituspäivän jälkeinen"
154
        not_same_project: "ei kuulu samaan projektiin"
155
        circular_dependency: "Tämä suhde loisi kehän."
156
        cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
157

  
158
  actionview_instancetag_blank_option: Valitse, ole hyvä
159

  
160
  general_text_No: 'Ei'
161
  general_text_Yes: 'Kyllä'
162
  general_text_no: 'ei'
163
  general_text_yes: 'kyllä'
164
  general_lang_name: 'Finnish (Suomi)'
165
  general_csv_separator: ','
166
  general_csv_decimal_separator: '.'
167
  general_csv_encoding: ISO-8859-15
168
  general_pdf_encoding: UTF-8
169
  general_first_day_of_week: '1'
170

  
171
  notice_account_updated: Tilin päivitys onnistui.
172
  notice_account_invalid_creditentials: Virheellinen käyttäjätunnus tai salasana
173
  notice_account_password_updated: Salasanan päivitys onnistui.
174
  notice_account_wrong_password: Väärä salasana
175
  notice_account_register_done: Tilin luonti onnistui. Aktivoidaksesi tilin seuraa linkkiä joka välitettiin sähköpostiisi.
176
  notice_account_unknown_email: Tuntematon käyttäjä.
177
  notice_can_t_change_password: Tämä tili käyttää ulkoista tunnistautumisjärjestelmää. Salasanaa ei voi muuttaa.
178
  notice_account_lost_email_sent: Sinulle on lähetetty sähköposti jossa on ohje kuinka vaihdat salasanasi.
179
  notice_account_activated: Tilisi on nyt aktivoitu, voit kirjautua sisälle.
180
  notice_successful_create: Luonti onnistui.
181
  notice_successful_update: Päivitys onnistui.
182
  notice_successful_delete: Poisto onnistui.
183
  notice_successful_connection: Yhteyden muodostus onnistui.
184
  notice_file_not_found: Hakemaasi sivua ei löytynyt tai se on poistettu.
185
  notice_locking_conflict: Toinen käyttäjä on päivittänyt tiedot.
186
  notice_not_authorized: Sinulla ei ole oikeutta näyttää tätä sivua.
187
  notice_email_sent: "Sähköposti on lähetty osoitteeseen %{value}"
188
  notice_email_error: "Sähköpostilähetyksessä tapahtui virhe (%{value})"
189
  notice_feeds_access_key_reseted: RSS salasana on nollaantunut.
190
  notice_failed_to_save_issues: "%{count} Tapahtum(an/ien) tallennus epäonnistui %{total} valitut: %{ids}."
191
  notice_no_issue_selected: "Tapahtumia ei ole valittu! Valitse tapahtumat joita haluat muokata."
192
  notice_account_pending: "Tilisi on luotu ja odottaa ylläpitäjän hyväksyntää."
193
  notice_default_data_loaded: Vakioasetusten palautus onnistui.
194

  
195
  error_can_t_load_default_data: "Vakioasetuksia ei voitu ladata: %{value}"
196
  error_scm_not_found: "Syötettä ja/tai versiota ei löydy tietovarastosta."
197
  error_scm_command_failed: "Tietovarastoon pääsyssä tapahtui virhe: %{value}"
198

  
199
  mail_subject_lost_password: "Sinun %{value} salasanasi"
200
  mail_body_lost_password: 'Vaihtaaksesi salasanasi, napsauta seuraavaa linkkiä:'
201
  mail_subject_register: "%{value} tilin aktivointi"
202
  mail_body_register: 'Aktivoidaksesi tilisi, napsauta seuraavaa linkkiä:'
203
  mail_body_account_information_external: "Voit nyt käyttää %{value} tiliäsi kirjautuaksesi järjestelmään."
204
  mail_body_account_information: Sinun tilin tiedot
205
  mail_subject_account_activation_request: "%{value} tilin aktivointi pyyntö"
206
  mail_body_account_activation_request: "Uusi käyttäjä (%{value}) on rekisteröitynyt. Hänen tili odottaa hyväksyntääsi:"
207

  
208

  
209
  field_name: Nimi
210
  field_description: Kuvaus
211
  field_summary: Yhteenveto
212
  field_is_required: Vaaditaan
213
  field_firstname: Etunimi
214
  field_lastname: Sukunimi
215
  field_mail: Sähköposti
216
  field_filename: Tiedosto
217
  field_filesize: Koko
218
  field_downloads: Latausta
219
  field_author: Tekijä
220
  field_created_on: Luotu
221
  field_updated_on: Päivitetty
222
  field_field_format: Muoto
223
  field_is_for_all: Kaikille projekteille
224
  field_possible_values: Mahdolliset arvot
225
  field_regexp: Säännöllinen lauseke (reg exp)
226
  field_min_length: Minimipituus
227
  field_max_length: Maksimipituus
228
  field_value: Arvo
229
  field_category: Luokka
230
  field_title: Otsikko
231
  field_project: Projekti
232
  field_issue: Tapahtuma
233
  field_status: Tila
234
  field_notes: Muistiinpanot
235
  field_is_closed: Tapahtuma suljettu
236
  field_is_default: Vakioarvo
237
  field_tracker: Tapahtuma
238
  field_subject: Aihe
239
  field_due_date: Määräaika
240
  field_assigned_to: Nimetty
241
  field_priority: Prioriteetti
242
  field_fixed_version: Kohdeversio
243
  field_user: Käyttäjä
244
  field_role: Rooli
245
  field_homepage: Kotisivu
246
  field_is_public: Julkinen
247
  field_parent: Aliprojekti
248
  field_is_in_roadmap: Tapahtumat näytetään roadmap näkymässä
249
  field_login: Kirjautuminen
250
  field_mail_notification: Sähköposti muistutukset
251
  field_admin: Ylläpitäjä
252
  field_last_login_on: Viimeinen yhteys
253
  field_language: Kieli
254
  field_effective_date: Päivä
255
  field_password: Salasana
256
  field_new_password: Uusi salasana
257
  field_password_confirmation: Vahvistus
258
  field_version: Versio
259
  field_type: Tyyppi
260
  field_host: Verkko-osoite
261
  field_port: Portti
262
  field_account: Tili
263
  field_base_dn: Base DN
264
  field_attr_login: Kirjautumismääre
265
  field_attr_firstname: Etuminenmääre
266
  field_attr_lastname: Sukunimenmääre
267
  field_attr_mail: Sähköpostinmääre
268
  field_onthefly: Automaattinen käyttäjien luonti
269
  field_start_date: Alku
270
  field_done_ratio: "% Tehty"
271
  field_auth_source: Varmennusmuoto
272
  field_hide_mail: Piiloita sähköpostiosoitteeni
273
  field_comments: Kommentti
274
  field_url: URL
275
  field_start_page: Aloitussivu
276
  field_subproject: Aliprojekti
277
  field_hours: Tuntia
278
  field_activity: Historia
279
  field_spent_on: Päivä
280
  field_identifier: Tunniste
281
  field_is_filter: Käytetään suodattimena
282
  field_issue_to: Liittyvä tapahtuma
283
  field_delay: Viive
284
  field_assignable: Tapahtumia voidaan nimetä tälle roolille
285
  field_redirect_existing_links: Uudelleenohjaa olemassa olevat linkit
286
  field_estimated_hours: Arvioitu aika
287
  field_column_names: Saraketta
288
  field_time_zone: Aikavyöhyke
289
  field_searchable: Haettava
290
  field_default_value: Vakioarvo
291

  
292
  setting_app_title: Ohjelman otsikko
293
  setting_app_subtitle: Ohjelman alaotsikko
294
  setting_welcome_text: Tervehdysteksti
295
  setting_default_language: Vakiokieli
296
  setting_login_required: Pakollinen kirjautuminen
297
  setting_self_registration: Itserekisteröinti
298
  setting_attachment_max_size: Liitteen maksimikoko
299
  setting_issues_export_limit: Tapahtumien vientirajoite
300
  setting_mail_from: Lähettäjän sähköpostiosoite
301
  setting_bcc_recipients: Vastaanottajat piilokopiona (bcc)
302
  setting_host_name: Verkko-osoite
303
  setting_text_formatting: Tekstin muotoilu
304
  setting_wiki_compression: Wiki historian pakkaus
305
  setting_feeds_limit: Syötteen sisällön raja
306
  setting_autofetch_changesets: Automaattisten muutosjoukkojen haku
307
  setting_sys_api_enabled: Salli WS tietovaraston hallintaan
308
  setting_commit_ref_keywords: Viittaavat hakusanat
309
  setting_commit_fix_keywords: Korjaavat hakusanat
310
  setting_autologin: Automaatinen kirjautuminen
311
  setting_date_format: Päivän muoto
312
  setting_time_format: Ajan muoto
313
  setting_cross_project_issue_relations: Salli projektien väliset tapahtuminen suhteet
314
  setting_issue_list_default_columns: Vakiosarakkeiden näyttö tapahtumalistauksessa
315
  setting_emails_footer: Sähköpostin alatunniste
316
  setting_protocol: Protokolla
317
  setting_per_page_options: Sivun objektien määrän asetukset
318

  
319
  label_user: Käyttäjä
320
  label_user_plural: Käyttäjät
321
  label_user_new: Uusi käyttäjä
322
  label_project: Projekti
323
  label_project_new: Uusi projekti
324
  label_project_plural: Projektit
325
  label_x_projects:
326
    zero:  no projects
327
    one:   1 project
328
    other: "%{count} projects"
329
  label_project_all: Kaikki projektit
330
  label_project_latest: Uusimmat projektit
331
  label_issue: Tapahtuma
332
  label_issue_new: Uusi tapahtuma
333
  label_issue_plural: Tapahtumat
334
  label_issue_view_all: Näytä kaikki tapahtumat
335
  label_issues_by: "Tapahtumat %{value}"
336
  label_document: Dokumentti
337
  label_document_new: Uusi dokumentti
338
  label_document_plural: Dokumentit
339
  label_role: Rooli
340
  label_role_plural: Roolit
341
  label_role_new: Uusi rooli
342
  label_role_and_permissions: Roolit ja oikeudet
343
  label_member: Jäsen
344
  label_member_new: Uusi jäsen
345
  label_member_plural: Jäsenet
346
  label_tracker: Tapahtuma
347
  label_tracker_plural: Tapahtumat
348
  label_tracker_new: Uusi tapahtuma
349
  label_workflow: Työnkulku
350
  label_issue_status: Tapahtuman tila
351
  label_issue_status_plural: Tapahtumien tilat
352
  label_issue_status_new: Uusi tila
353
  label_issue_category: Tapahtumaluokka
354
  label_issue_category_plural: Tapahtumaluokat
355
  label_issue_category_new: Uusi luokka
356
  label_custom_field: Räätälöity kenttä
357
  label_custom_field_plural: Räätälöidyt kentät
358
  label_custom_field_new: Uusi räätälöity kenttä
359
  label_enumerations: Lista
360
  label_enumeration_new: Uusi arvo
361
  label_information: Tieto
362
  label_information_plural: Tiedot
363
  label_please_login: Kirjaudu ole hyvä
364
  label_register: Rekisteröidy
365
  label_password_lost: Hukattu salasana
366
  label_home: Koti
367
  label_my_page: Omasivu
368
  label_my_account: Oma tili
369
  label_my_projects: Omat projektit
370
  label_administration: Ylläpito
371
  label_login: Kirjaudu sisään
372
  label_logout: Kirjaudu ulos
373
  label_help: Ohjeet
374
  label_reported_issues: Raportoidut tapahtumat
375
  label_assigned_to_me_issues: Minulle nimetyt tapahtumat
376
  label_last_login: Viimeinen yhteys
377
  label_registered_on: Rekisteröity
378
  label_activity: Historia
379
  label_new: Uusi
380
  label_logged_as: Kirjauduttu nimellä
381
  label_environment: Ympäristö
382
  label_authentication: Varmennus
383
  label_auth_source: Varmennustapa
384
  label_auth_source_new: Uusi varmennustapa
385
  label_auth_source_plural: Varmennustavat
386
  label_subproject_plural: Aliprojektit
387
  label_min_max_length: Min - Max pituudet
388
  label_list: Lista
389
  label_date: Päivä
390
  label_integer: Kokonaisluku
391
  label_float: Liukuluku
392
  label_boolean: Totuusarvomuuttuja
393
  label_string: Merkkijono
394
  label_text: Pitkä merkkijono
395
  label_attribute: Määre
396
  label_attribute_plural: Määreet
397
  label_no_data: Ei tietoa näytettäväksi
398
  label_change_status: Muutos tila
399
  label_history: Historia
400
  label_attachment: Tiedosto
401
  label_attachment_new: Uusi tiedosto
402
  label_attachment_delete: Poista tiedosto
403
  label_attachment_plural: Tiedostot
404
  label_report: Raportti
405
  label_report_plural: Raportit
406
  label_news: Uutinen
407
  label_news_new: Lisää uutinen
408
  label_news_plural: Uutiset
409
  label_news_latest: Viimeisimmät uutiset
410
  label_news_view_all: Näytä kaikki uutiset
411
  label_settings: Asetukset
412
  label_overview: Yleiskatsaus
413
  label_version: Versio
414
  label_version_new: Uusi versio
415
  label_version_plural: Versiot
416
  label_confirmation: Vahvistus
417
  label_export_to: Vie
418
  label_read: Lukee...
419
  label_public_projects: Julkiset projektit
420
  label_open_issues: avoin, yhteensä
421
  label_open_issues_plural: avointa, yhteensä
422
  label_closed_issues: suljettu
423
  label_closed_issues_plural: suljettua
424
  label_x_open_issues_abbr_on_total:
425
    zero:  0 open / %{total}
426
    one:   1 open / %{total}
427
    other: "%{count} open / %{total}"
428
  label_x_open_issues_abbr:
429
    zero:  0 open
430
    one:   1 open
431
    other: "%{count} open"
432
  label_x_closed_issues_abbr:
433
    zero:  0 closed
434
    one:   1 closed
435
    other: "%{count} closed"
436
  label_total: Yhteensä
437
  label_permissions: Oikeudet
438
  label_current_status: Nykyinen tila
439
  label_new_statuses_allowed: Uudet tilat sallittu
440
  label_all: kaikki
441
  label_none: ei mitään
442
  label_nobody: ei kukaan
443
  label_next: Seuraava
444
  label_previous: Edellinen
445
  label_used_by: Käytetty
446
  label_details: Yksityiskohdat
447
  label_add_note: Lisää muistiinpano
448
  label_per_page: Per sivu
449
  label_calendar: Kalenteri
450
  label_months_from: kuukauden päässä
451
  label_gantt: Gantt
452
  label_internal: Sisäinen
453
  label_last_changes: "viimeiset %{count} muutokset"
454
  label_change_view_all: Näytä kaikki muutokset
455
  label_personalize_page: Personoi tämä sivu
456
  label_comment: Kommentti
457
  label_comment_plural: Kommentit
458
  label_x_comments:
459
    zero: no comments
460
    one: 1 comment
461
    other: "%{count} comments"
462
  label_comment_add: Lisää kommentti
463
  label_comment_added: Kommentti lisätty
464
  label_comment_delete: Poista kommentti
465
  label_query: Räätälöity haku
466
  label_query_plural: Räätälöidyt haut
467
  label_query_new: Uusi haku
468
  label_filter_add: Lisää suodatin
469
  label_filter_plural: Suodattimet
470
  label_equals: sama kuin
471
  label_not_equals: eri kuin
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff