Merge branch 'dev' into implementation/61533-extend-data-model-to-allow-logging-time-on-other-resources

This commit is contained in:
Klaus Zanders
2025-07-10 11:51:16 +02:00
committed by GitHub
571 changed files with 31070 additions and 20633 deletions
-4
View File
@@ -138,9 +138,6 @@ gem "rack-protection", "~> 3.2.0"
# https://github.com/kickstarter/rack-attack
gem "rack-attack", "~> 6.7.0"
# CSP headers
gem "secure_headers", "~> 7.1.0"
# Browser detection for incompatibility checks
gem "browser", "~> 6.2.0"
@@ -217,7 +214,6 @@ gem "mini_magick", "~> 5.2.0", require: false
gem "validate_url"
# Storages support code
gem "dry-auto_inject"
gem "dry-container"
gem "dry-monads"
gem "dry-validation"
-8
View File
@@ -481,9 +481,6 @@ GEM
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.3)
dry-auto_inject (1.1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-configurable (1.3.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
@@ -1167,7 +1164,6 @@ GEM
nokogiri (>= 1.16.8)
scimitar (2.11.0)
rails (>= 7.0)
secure_headers (7.1.0)
securerandom (0.4.1)
selenium-devtools (0.138.0)
selenium-webdriver (~> 4.2)
@@ -1383,7 +1379,6 @@ DEPENDENCIES
disposable (~> 0.6.2)
doorkeeper (~> 5.8.0)
dotenv-rails
dry-auto_inject
dry-container
dry-monads
dry-validation
@@ -1516,7 +1511,6 @@ DEPENDENCIES
rubytree (~> 2.1.0)
sanitize (~> 7.0.0)
scimitar (~> 2.11)
secure_headers (~> 7.1.0)
selenium-devtools
selenium-webdriver (~> 4.20)
semantic (~> 1.6.1)
@@ -1658,7 +1652,6 @@ CHECKSUMS
dotenv (3.1.8) sha256=9e1176060ced581f8e6ce4384e91361817763a76e3c625c8bddc18b35bd392c3
dotenv-rails (3.1.8) sha256=46c9d1226a8b58a83b5f61325aa8cffd25cea1c0fafdfbbbee1e5dfea77980c4
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
dry-auto_inject (1.1.0) sha256=f9276cb5d15a3ef138e1f1149e289e287f636de57ef4a6decd233542eb708f78
dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8
dry-container (0.11.0) sha256=23be9381644d47343f3bf13b082b4255994ada0bfd88e0737eaaadc99d035229
dry-core (1.1.0) sha256=0903821a9707649a7da545a2cd88e20f3a663ab1c5288abd7f914fa7751ab195
@@ -1949,7 +1942,6 @@ CHECKSUMS
safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce
sanitize (7.0.0) sha256=269d1b9d7326e69307723af5643ec032ff86ad616e72a3b36d301ac75a273984
scimitar (2.11.0) sha256=77cf779a843be7d572046acdcf0a1829bd3b1c33db993fa83faf7f1863d8c625
secure_headers (7.1.0) sha256=6b1f9d5f9507af2948f4636452c41c09371927836396c2185438ffdf0a731124
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
selenium-devtools (0.138.0) sha256=596c08e114342dd89513bc3d18bbb2ae39e532864dbc25c09a48fb9922fc5b7b
selenium-webdriver (4.34.0) sha256=ec7bb718cbe66fe2b247d8ca5e6ba26caed0976d76579d7cb2fadd8dae8b271e
@@ -8,7 +8,6 @@
mobile_label: I18n.t(:label_setting_plural),
href: my_notifications_path,
size: :medium,
target: "_blank",
aria: { label: I18n.t("js.notifications.settings.title") }
) do |button|
button.with_leading_visual_icon(icon: :gear)
@@ -67,8 +67,9 @@ See COPYRIGHT and LICENSE files for more details.
component.with_row(scheme: :default) { render_blank_slate }
else
rows.each do |row|
component.with_row(scheme: :default) do
render(row_class.new(row:, table: self))
row_instance = row_class.new(row: row, table: self)
component.with_row(scheme: row_instance.scheme) do
render(row_instance)
end
end
end
+4
View File
@@ -68,6 +68,10 @@ class RowComponent < ApplicationComponent
nil
end
def scheme
:default
end
def checkmark(condition)
if condition
helpers.op_icon "icon icon-checkmark"
@@ -0,0 +1,97 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
module Types
class RowComponent < ::RowComponent
include ApplicationHelper
include TypesHelper
def column_css_classes
super.merge(
name: "timelines-pet-name",
color: "timelines-pet-color",
default: "timelines-pet-is_default",
milestone: "timelines-pet-is_milestone",
sort: "timelines-pet-reorder"
)
end
def name
link_to model.name, edit_type_settings_path(type_id: model.id)
end
def workflow_warning
return unless model.workflows.empty?
safe_join([
op_icon("icon3 icon-warning"),
t(:text_type_no_workflow),
" (",
link_to(t(:button_edit), edit_workflows_path(type: model)),
")"
])
end
def color
icon_for_type model
end
def default
checked_image model.is_default
end
def milestone
checked_image model.is_milestone
end
def sort
helpers.reorder_links("type", { action: "move", id: model })
end
def button_links
[delete_link]
end
def delete_link
return if model.is_standard?
link_to(
"",
model,
method: :delete,
data: { confirm: t(:text_are_you_sure) },
class: "icon icon-delete",
title: t(:button_delete)
)
end
end
end
end
@@ -28,28 +28,33 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Storages
module Peripherals
module StorageInteraction
module Inputs
class SetPermissionsContract < Dry::Validation::Contract
params do
required(:file_id).filled(:string)
required(:user_permissions).array(:hash) do
optional(:user_id).filled(:string)
optional(:group_id).filled(:string)
required(:permissions)
.array(:symbol, included_in?: OpenProject::Storages::Engine.external_file_permissions)
end
end
module WorkPackageTypes
module Types
class TableComponent < ::TableComponent
columns :name, :color, :workflow_warning, :default, :milestone, :sort
rule(:user_permissions).each do
both = value.key?(:user_id) && value.key?(:group_id)
none = !value.key?(:user_id) && !value.key?(:group_id)
def headers
[
[:name, { caption: Type.human_attribute_name(:name) }],
[:color, { caption: Type.human_attribute_name(:color) }],
[:workflow_warning, { caption: "Workflow" }],
[:default, { caption: I18n.t(:label_active_in_new_projects) }],
[:milestone, { caption: Type.human_attribute_name(:is_milestone) }],
[:sort, { caption: I18n.t(:button_sort) }]
]
end
key.failure("must have either user_id or group_id") if both || none
end
end
def header_options(name)
headers_hash = headers.to_h
headers_hash[name.to_sym] || { caption: name.to_s }
end
def mobile_title
I18n.t(:label_type_plural)
end
def sortable?
false
end
end
end
@@ -36,6 +36,8 @@ module WorkPackageTypes
attribute :is_in_roadmap
attribute :is_milestone
attribute :name
attribute :project_ids
attribute :attribute_groups
validates :name, presence: true, length: { maximum: 255 }
validates :is_default, :is_milestone, :is_in_roadmap, inclusion: { in: [true, false] }
@@ -56,6 +56,7 @@ class ApplicationController < ActionController::Base
include OpenProjectErrorHelper
include Security::DefaultUrlOptions
include OpModalFlashable
include DynamicContentSecurityPolicy
layout "base"
@@ -0,0 +1,50 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module DynamicContentSecurityPolicy
##
# Dynamically append sources to CSP directives
# This replaces the secure_headers named append functionality
def append_content_security_policy_directives(directives)
policy = current_content_security_policy
directives.each do |directive, source_values|
current_value = policy.send(directive) || policy.directives["default-src"]
new_values =
if current_value == %w('none') # rubocop:disable Lint/PercentStringArray
source_values.compact.uniq
else
(current_value + source_values).compact.uniq
end
policy.send(directive, *new_values)
request.content_security_policy = policy
end
end
end
@@ -118,6 +118,10 @@ module OpTurbo
.render_in(view_context)
end
def reload_page_via_turbo_stream
turbo_streams << OpTurbo::StreamComponent.new(action: :reloadPage, target: nil).render_in(view_context)
end
def turbo_streams
@turbo_streams ||= []
end
+1 -2
View File
@@ -82,8 +82,7 @@ class OAuthClientsController < ApplicationController
storage = oauth_client.integration
# check if the origin is the same
destination_url = destination_url(params.fetch(:destination_url, ""))
auth_state = ::Storages::Peripherals::StorageInteraction::Authentication
.authorization_state(storage:, user: User.current)
auth_state = ::Storages::Adapters::Authentication.authorization_state(storage:, user: User.current)
if auth_state == :connected
redirect_to(destination_url)
@@ -30,6 +30,6 @@ class Projects::Settings::VersionsController < Projects::SettingsController
menu_item :settings_versions
def show
@versions = @project.shared_versions.order_by_semver_name
@versions = @project.shared_versions.order(:name)
end
end
+1 -2
View File
@@ -255,8 +255,7 @@ class RepositoriesController < ApplicationController
end
def stats
# allow object_src self to be able to load dynamic stats SVGs from ./graph
override_content_security_policy_directives object_src: %w('self')
append_content_security_policy_directives object_src: %w('self') # rubocop:disable Lint/PercentStringArray
@show_commits_per_author = current_user.allowed_in_project?(:view_commit_author_statistics, @project)
end
+1 -1
View File
@@ -49,7 +49,7 @@ class SearchController < ApplicationController
provision_gon
render layout: layout_non_or_no_menu
render "index", locals: { menu_name: project_or_global_menu }
end
private
-170
View File
@@ -1,170 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class TypesController < ApplicationController
include PaginationHelper
layout "admin"
before_action :require_admin
before_action :find_type, only: %i[update move destroy]
def index
@types = ::Type.page(page_param).per_page(per_page_param)
end
def type
@type
end
def new
@type = Type.new(params[:type])
load_projects_and_types
end
def edit
if params[:tab].blank?
redirect_to tab: :settings
else
type = ::Type.includes(:projects, :custom_fields).find(params[:id])
render_edit_tab(type)
end
end
def create
additional_params = {}
value = params.dig(:type, :copy_workflow_from)
additional_params[:copy_workflow_from] = value if value.present?
service_call = WorkPackageTypes::CreateService
.new(user: current_user)
.call(permitted_type_params.merge(additional_params))
@type = service_call.result
if service_call.success?
redirect_to edit_type_settings_path(@type), notice: t(:notice_successful_create), status: :see_other
else
render action: :new, status: :unprocessable_entity
end
end
def update
UpdateTypeService
.new(@type, current_user)
.call(permitted_type_params) do |call|
call.on_success { redirect_to_type_tab_path(@type, t(:notice_successful_update)) }
call.on_failure { render_edit_tab(@type, status: :unprocessable_entity) }
end
end
def move
if @type.update(permitted_params.type_move)
flash[:notice] = I18n.t(:notice_successful_update)
redirect_to types_path
else
flash.now[:error] = I18n.t(:error_type_could_not_be_saved)
render action: :edit, status: :unprocessable_entity
end
end
def destroy
# types cannot be deleted when they have work packages
# or they are standard types
# put that into the model and do a `if @type.destroy`
if @type.work_packages.empty? && !@type.is_standard?
@type.destroy
flash[:notice] = I18n.t(:notice_successful_delete)
else
flash[:error] = destroy_error_message
end
redirect_to action: "index"
end
protected
def find_type
@type = ::Type.find(params[:id])
end
def permitted_type_params
# having to call #to_unsafe_h as a query hash the attribute_groups
# parameters would otherwise still be an ActiveSupport::Parameter
permitted_params.type.to_unsafe_h
end
def load_projects_and_types
@types = ::Type.order(Arel.sql("position"))
@projects = Project.all
end
def redirect_to_type_tab_path(type, notice)
tab = params["tab"] || "settings"
redirect_to(edit_tab_type_path(type, tab:), notice:, status: :see_other)
end
def render_edit_tab(type, status: :ok)
@tab = params[:tab]
@projects = Project.all
@type = type
render action: :edit, status:
end
def destroy_error_message
if @type.is_standard?
t(:error_can_not_delete_standard_type)
else
error_message = [
ApplicationController.helpers.sanitize(
t(:"error_can_not_delete_type.explanation", url: belonging_wps_url(@type.id)),
attributes: %w(href target)
)
]
if archived_projects.any?
error_message << ApplicationController.helpers.sanitize(
t(:error_can_not_delete_in_use_archived_work_packages,
archived_projects_urls: helpers.archived_projects_urls_for(archived_projects)),
attributes: %w(href target)
)
end
error_message
end
end
def belonging_wps_url(type_id)
work_packages_path query_props: { f: [{ n: "type", o: "=", v: [type_id] }] }.to_json
end
def archived_projects
@archived_projects ||= @type.projects.archived
end
end
+1 -1
View File
@@ -168,7 +168,7 @@ class VersionsController < ApplicationController
versions = versions.or(@project.rolled_up_versions.includes(:custom_values))
end
versions = versions.visible.order_by_semver_name.except(:distinct).uniq
versions = versions.visible.order(:name).except(:distinct).uniq
versions.reject! { |version| version.closed? || version.completed? } unless completed
versions
end
@@ -31,10 +31,18 @@
module WorkPackageTypes
class PdfExportTemplateController < ApplicationController
include OpTurbo::ComponentStream
layout "admin"
before_action :require_admin
before_action :find_type, only: %i[toggle drop enable_all disable_all]
before_action :find_type, only: %i[edit toggle drop enable_all disable_all]
before_action :find_template, only: %i[toggle drop]
current_menu_item do
:types
end
def edit; end
def enable_all
return render_404_turbo_stream if @type.nil?
@@ -0,0 +1,147 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class TypesController < ApplicationController
include PaginationHelper
layout "admin"
before_action :require_admin
before_action :find_type, only: %i[move destroy]
current_menu_item do
:types
end
def index
@types = ::Type
.includes(:workflows, :projects, :custom_fields, :color)
.page(page_param)
.per_page(per_page_param)
end
def type
@type
end
def new
@type = Type.new(params[:type])
load_projects_and_types
end
def create
additional_params = {}
value = params.dig(:type, :copy_workflow_from)
additional_params[:copy_workflow_from] = value if value.present?
service_call = WorkPackageTypes::CreateService
.new(user: current_user)
.call(permitted_type_params.merge(additional_params))
@type = service_call.result
if service_call.success?
redirect_to edit_type_settings_path(@type), notice: t(:notice_successful_create), status: :see_other
else
render action: :new, status: :unprocessable_entity
end
end
def move
if @type.update(permitted_params.type_move)
flash[:notice] = I18n.t(:notice_successful_update)
else
flash.now[:error] = I18n.t(:error_type_could_not_be_saved)
end
redirect_to types_path
end
def destroy
# types cannot be deleted when they have work packages
# or they are standard types
# put that into the model and do a `if @type.destroy`
if @type.work_packages.empty? && !@type.is_standard?
@type.destroy
flash[:notice] = I18n.t(:notice_successful_delete)
else
flash[:error] = destroy_error_message
end
redirect_to action: "index"
end
protected
def find_type
@type = ::Type.find(params[:id])
end
def permitted_type_params
# having to call #to_unsafe_h as a query hash the attribute_groups
# parameters would otherwise still be an ActiveSupport::Parameter
permitted_params.type.to_unsafe_h
end
def load_projects_and_types
@types = ::Type.order(Arel.sql("position"))
@projects = Project.all
end
def destroy_error_message
if @type.is_standard?
t(:error_can_not_delete_standard_type)
else
error_message = [
ApplicationController.helpers.sanitize(
t(:"error_can_not_delete_type.explanation", url: belonging_wps_url(@type.id)),
attributes: %w(href target)
)
]
if archived_projects.any?
error_message << ApplicationController.helpers.sanitize(
t(:error_can_not_delete_in_use_archived_work_packages,
archived_projects_urls: helpers.archived_projects_urls_for(archived_projects)),
attributes: %w(href target)
)
end
error_message
end
end
def belonging_wps_url(type_id)
work_packages_path query_props: { f: [{ n: "type", o: "=", v: [type_id] }] }.to_json
end
def archived_projects
@archived_projects ||= @type.projects.archived
end
end
end
+6
View File
@@ -219,6 +219,12 @@ module ApplicationHelper
end
end
# Backward compatibility helper for secure_headers gem migration
# Rails built-in CSP equivalent of nonced_javascript_tag
def nonced_javascript_tag(**, &)
javascript_tag(nonce: true, **, &)
end
def to_path_param(path)
path.to_s
end
+1 -1
View File
@@ -66,7 +66,7 @@ module FrontendAssetHelper
end
def nonced_javascript_include_tag(path, **)
javascript_include_tag(path, nonce: content_security_policy_script_nonce, **)
javascript_include_tag(path, nonce: content_security_policy_nonce, **)
end
private
+2 -4
View File
@@ -29,10 +29,8 @@
module SecureHeadersHelper
##
# Output a rails +csp_meta_tag+ compatible tag
# while we're still using the +secure_headers+ gem.
# using Rails built-in CSP functionality.
def secure_header_csp_meta_tag
tag :meta,
name: "csp-nonce",
content: content_security_policy_script_nonce
csp_meta_tag
end
end
+1 -1
View File
@@ -55,7 +55,7 @@ module ::TypesHelper
},
{
name: "export_configuration",
path: edit_tab_type_path(id: @type.id, tab: :export_configuration),
path: edit_type_pdf_export_template_index_path(type_id: @type.id),
label: I18n.t("types.edit.export_configuration.tab"),
view_component: WorkPackageTypes::ExportConfigurationComponent
}
+2 -2
View File
@@ -68,10 +68,10 @@ module Projects::Versions
def assignable_versions(only_open: true)
if only_open
@assignable_versions ||=
shared_versions.references(:project).with_status_open.order_by_semver_name.to_a
shared_versions.references(:project).with_status_open.order(:name).to_a
else
@assignable_versions_including_non_open ||= # rubocop:disable Naming/MemoizedInstanceVariableName
shared_versions.references(:project).order_by_semver_name.to_a
shared_versions.references(:project).order(:name).to_a
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -30,16 +32,6 @@ class Queries::Versions::Orders::DefaultOrder < Queries::Orders::Base
self.model = Version
def self.key
/\A(id|name|semver_name)\z/
end
def initialize(attribute)
if attribute == :semver_name
OpenProject::Deprecation.warn("Sorting by semver_name is deprecated, name should be used instead")
super(:name)
else
super
end
/\A(id|name)\z/
end
end
+3
View File
@@ -35,4 +35,7 @@ class RemoteIdentity < ApplicationRecord
validates :user, uniqueness: { scope: %i[auth_source integration] }
validates :origin_user_id, :user, :auth_source, :integration, presence: true
# FIXME: This needs a better name - 2025.03.18 @mereghost
scope :of_user_and_client, ->(user, client, integration) { find_by(user:, auth_source: client, integration:) }
end
+4
View File
@@ -317,6 +317,10 @@ class User < Principal
authentication_provider&.display_name
end
def provided_by_oidc?
authentication_provider.is_a?(OpenIDConnect::Provider)
end
##
# Allows the API and other sources to determine locking actions
# on represented collections of children of Principals.
+1 -2
View File
@@ -46,8 +46,7 @@ class Version < ApplicationRecord
validates :status, inclusion: { in: VERSION_STATUSES }
validate :validate_start_date_before_effective_date
scopes :order_by_semver_name,
:rolled_up,
scopes :rolled_up,
:shared_with
# Returns versions that are either:
-192
View File
@@ -1,192 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class BaseTypeService
include Shared::BlockService
include Contracted
attr_accessor :contract_class, :type, :user
def initialize(type, user)
self.type = type
self.user = user
self.contract_class = ::Types::BaseContract
end
def call(params, options, &)
result = update(params, options)
block_with_result(result, &)
end
private
def update(params, options)
success = false
errors = type.errors
Type.transaction do
success, errors = set_params_and_validate(params)
if success
after_type_save(params, options)
else
raise(ActiveRecord::Rollback)
end
end
ServiceResult.new(success:,
errors:,
result: type)
rescue StandardError => e
ServiceResult.failure.tap do |result|
result.errors.add(:base, e.message)
end
end
def set_params_and_validate(params)
# Only set attribute groups when it exists
# (Regression #28400)
set_attribute_groups(params) if params[:attribute_groups]
# This should go before `set_scalar_params` call to get the
# project_ids, custom_field_ids diffs from the type and the params.
# For determining the active custom fields for the type, it is necessary
# to know whether the type is a milestone or not.
set_milestone_param(params) unless params[:is_milestone].nil?
set_active_custom_fields
set_active_custom_fields_for_project_ids(params[:project_ids]) if params[:project_ids].present?
set_scalar_params(params)
validate_and_save(type, user)
end
def set_milestone_param(params)
type.is_milestone = params[:is_milestone]
end
def set_scalar_params(params)
type.attributes = params.except(:attribute_groups, :subject_configuration, :subject_pattern)
end
def set_attribute_groups(params)
if params[:attribute_groups].empty?
type.reset_attribute_groups
else
type.attribute_groups = parse_attribute_groups_params(params)
end
end
def parse_attribute_groups_params(params)
return if params[:attribute_groups].nil?
transform_attribute_groups(params[:attribute_groups])
end
def after_type_save(_params, _options)
# noop to be overwritten by subclasses
end
def transform_attribute_groups(groups)
groups.map do |group|
if group["type"] == "query"
transform_query_group(group)
else
transform_attribute_group(group)
end
end
end
def transform_attribute_group(group)
name =
if group["key"]
group["key"].to_sym
else
group["name"]
end
[
name,
group["attributes"].pluck("key")
]
end
def transform_query_group(group)
name = group["name"]
props = JSON.parse group["query"]
query = Query.new_default(name: "Embedded table: #{name}")
query.extend(OpenProject::ChangedBySystem)
query.change_by_system do
query.user = User.system
end
::API::V3::UpdateQueryFromV3ParamsService
.new(query, user)
.call(props.with_indifferent_access)
query.show_hierarchies = false
[
name,
[query]
]
end
##
# Syncs attribute group settings for custom fields with enabled custom fields
# for this type. If a custom field is not in a group, it is removed from the
# custom_field_ids list.
def set_active_custom_fields
type.custom_field_ids = type
.attribute_groups
.flat_map(&:members)
.select { CustomField.custom_field_attribute? it }
.map { it.gsub(/^custom_field_/, "").to_i }
.uniq
end
def set_active_custom_fields_for_project_ids(project_ids)
new_project_ids_to_activate_cfs = project_ids.reject(&:empty?).map(&:to_i) - type.project_ids
values = Project
.where(id: new_project_ids_to_activate_cfs)
.to_a
.product(type.custom_field_ids)
.map { |p, cf_ids| { project_id: p.id, custom_field_id: cf_ids } }
return if values.empty?
CustomFieldsProject.insert_all(values)
end
end
@@ -47,10 +47,11 @@ module RemoteIdentities
def call
if @model.new_record? || @force_update
user_id = @integration.extract_origin_user_id(@token)
return user_id if user_id.failure?
origin_result = @integration.extract_origin_user_id(@token)
@model.origin_user_id = user_id.result
user_id = origin_result.value_or { return ServiceResult.failure(errors: it) }
@model.origin_user_id = user_id
return success unless @model.changed?
return failure unless @model.save
-77
View File
@@ -1,77 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class UpdateTypeService < BaseTypeService
def call(params)
# forbid renaming if it is a standard type
if params[:type] && type.is_standard?
params[:type].delete :name
end
super(params, {})
end
private
def set_params_and_validate(params)
# Set patterns includes a data validation before assigning the value to the attribute.
# A validation failure should return a service call failure.
patterns = params[:patterns]
if patterns.present?
validate_enterprise_action(patterns)
set_patterns(patterns)
return [false, type.errors] if type.errors.any?
end
super
end
def validate_enterprise_action(patterns)
change_from_manual_to_generated = !type.patterns.subject&.enabled? && patterns.dig(:subject, :enabled)
action = :work_package_subject_generation
if change_from_manual_to_generated && !EnterpriseToken.allows_to?(action)
type.errors.add(:patterns, :error_enterprise_only, action: action.to_s.titleize)
end
end
def set_patterns(patterns)
WorkPackageTypes::Patterns::Collection
.build(patterns:)
.either(
->(collection) { type.patterns = collection },
->(result) do
result.errors(full: true).messages.each do |message|
type.errors.add(:patterns, message.text)
end
end
)
end
end
@@ -85,7 +85,7 @@ module WorkPackageTypes
def check_projects(params)
return unless params.key?(:project_ids)
invalid_project_ids = params[:project_ids].reject { |id| Project.exists?(id) }
invalid_project_ids = params[:project_ids].reject { |id| id.blank? || Project.exists?(id) }
unless invalid_project_ids.empty?
@param_validations.update({ project_ids: "Projects with ids #{invalid_project_ids.join(', ')} do not exist." })
end
+1 -1
View File
@@ -31,7 +31,7 @@
<%= render "common/favicons" %>
<%# Allow gon output when necessary %>
<%= include_gon(nonce: content_security_policy_script_nonce) %>
<%= include_gon(nonce: content_security_policy_nonce) %>
<%# Include CLI assets (development) or prod build assets %>
<%= include_frontend_assets %>
@@ -96,7 +96,7 @@ See COPYRIGHT and LICENSE files for more details.
</td>
<td>
<% if current_user.admin? %>
<% type_links = custom_field.types.map { |t| link_to(t.name, edit_tab_type_path(id: t.id, tab: "form_configuration")) } %>
<% type_links = custom_field.types.map { |t| link_to(t.name, edit_type_form_configuration_path(t)) } %>
<%= safe_join type_links, ", " %>
<% else %>
<%= custom_field.types.map(&:name).join(", ") %>
-170
View File
@@ -1,170 +0,0 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% html_title t(:label_administration), t("label_type_plural") %>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_type_plural) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) },
t(:label_type_plural)]
)
end
%>
<%=
render(Primer::OpenProject::SubHeader.new) do |subheader|
subheader.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: t(:label_type_new),
test_selector: "op-admin-types--button-new",
tag: :a,
href: new_type_path
) do
t("activerecord.attributes.work_package.type")
end
end
%>
<% if @types.any? %>
<div class="generic-table--container">
<div class="generic-table--results-container">
<table class="generic-table" data-controller="table-highlighting">
<colgroup>
<col>
<col data-highlight="false">
<col>
<col>
<col>
<col>
<col>
<col data-highlight="false">
</colgroup>
<thead>
<tr>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Type.human_attribute_name(:name) %>
</span>
</div>
</div>
</th>
<th><div class="generic-table--empty-header"></div></th>
<!-- Missing workflow warning -->
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Type.human_attribute_name(:color) %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= t(:label_active_in_new_projects) %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Type.human_attribute_name(:is_milestone) %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= t(:button_sort) %>
</span>
</div>
</div>
</th>
<th><div class="generic-table--empty-header"></div></th>
</tr>
</thead>
<tbody>
<% for type in @types %>
<tr>
<td class="timelines-pet-name">
<%= link_to type.name, edit_type_settings_path(type_id: type.id) %>
</td>
<td>
<% if type.workflows.empty? %>
<span class="icon-context icon-warning">
<%= t(:text_type_no_workflow) %>
(<%= link_to t(:button_edit), { controller: "/workflows",
action: "edit",
type_id: type } %>)
</span>
<% end %>
</td>
<td class="timelines-pet-color">
<%= icon_for_type type %>
</td>
<td class="timelines-pet-is_default">
<%= checked_image(type.is_default) %>
</td>
<td class="timelines-pet-is_milestone">
<%= checked_image(type.is_milestone) %>
</td>
<td class="timelines-pet-reorder">
<%= reorder_links("type", { action: "move", id: type }) %>
</td>
<td class="buttons">
<% if !type.is_standard? %>
<%= link_to "", type,
method: :delete,
data: { confirm: t(:text_are_you_sure) },
class: "icon icon-delete",
title: t(:button_delete) %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<%= pagination_links_full @types %>
<% else %>
<%= no_results_box(action_url: new_type_path, display_action: true) %>
<% end %>
@@ -29,24 +29,5 @@ See COPYRIGHT and LICENSE files for more details.
<% html_title t(:label_administration), "#{t(:label_edit)} #{t(:label_work_package_types)} #{h @type.name}" %>
<% tabs = types_tabs %>
<%= render ::Types::EditPageHeaderComponent.new(type: @type, tabs:) %>
<%=
selected_tab = selected_tab(tabs)
form_data = {
subject_configuration_form_data: params[:types_forms_subject_configuration_form_model]
}
if selected_tab.key?(:view_component)
render selected_tab[:view_component].new(@type, **form_data)
else
form_for @type,
url: update_tab_type_path(id: @type.id, tab: @tab),
builder: TabularFormBuilder,
lang: current_language do |f|
render partial: "common/tabs", locals: { f:, tabs:, selected_tab:, with_tab_nav: false }
end
end
%>
<%= render ::Types::EditPageHeaderComponent.new(type: @type, tabs: types_tabs) %>
<%= render WorkPackageTypes::ExportConfigurationComponent.new(@type) %>
@@ -0,0 +1,64 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% html_title t(:label_administration), t("label_type_plural") %>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_type_plural) }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) },
t(:label_type_plural)]
)
end
%>
<%=
render(Primer::OpenProject::SubHeader.new) do |subheader|
subheader.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: t(:label_type_new),
test_selector: "op-admin-types--button-new",
tag: :a,
href: new_type_path
) do
t("activerecord.attributes.work_package.type")
end
end
%>
<% if @types.any? %>
<%= render WorkPackageTypes::Types::TableComponent.new(rows: @types) %>
<% else %>
<%= no_results_box(
action_url: new_type_path,
display_action: true,
custom_action_text: t("types.index.no_results_content_text")) %>
<% end %>
+11 -2
View File
@@ -29,7 +29,16 @@ See COPYRIGHT and LICENSE files for more details.
<% html_title t(:label_bulk_edit_selected_work_packages) %>
<h2><%= t(:label_bulk_edit_selected_work_packages) %></h2>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_bulk_edit_selected_work_packages) }
header.with_breadcrumbs(
[*([href: project_overview_path(@project.id), text: @project.name] if @project),
{ href: (@project ? project_work_packages_path(@project) : work_packages_path), text: t(:label_work_package_plural) },
t(:label_bulk_edit_selected_work_packages)]
)
end
%>
<ul>
<% @work_packages.each do |wp| %>
<li>
@@ -132,7 +141,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_select_tag(
"work_package[version_id]",
content_tag("option", t(:label_none), value: "none") +
version_options_for_select(@project.shared_versions.with_status_open.order_by_semver_name),
version_options_for_select(@project.shared_versions.with_status_open.order(:name)),
include_blank: t(:label_no_change_option)
) %>
</div>
+16 -3
View File
@@ -29,7 +29,20 @@ See COPYRIGHT and LICENSE files for more details.
<% html_title @copy ? t(:button_duplicate) : t(:button_move), t("activerecord.models.work_package") %>
<h2><%= @copy ? t(:button_duplicate) : t(:button_move) %></h2>
<% single_wp = @work_packages.size == 1 ? @work_packages.first : nil %>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { @copy ? t(:button_duplicate) : t(:button_move) }
header.with_breadcrumbs(
[*([href: project_overview_path(@project.id), text: @project.name] if @project),
{ href: project_work_packages_path(@project), text: t(:label_work_package_plural) },
*(single_wp ? [{ href: work_package_path(single_wp), text: single_wp.subject }] : []),
@copy ? t(:button_duplicate) : t(:button_move)]
)
end
%>
<ul>
<% @work_packages.each do |work_package| -%>
<li>
@@ -162,8 +175,8 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_label_tag :budget_id, Budget.model_name.human %>
<%= styled_select_tag(
"budget_id", (@target_project == @project ? content_tag("option", t(:label_no_change_option), value: "") : "") +
content_tag("option", t(:label_none), value: "none") +
options_from_collection_for_select(@target_project.budgets, :id, :subject)
content_tag("option", t(:label_none), value: "none") +
options_from_collection_for_select(@target_project.budgets, :id, :subject)
) %>
</div>
</div>
-3
View File
@@ -125,9 +125,6 @@ module OpenProject
# http://stackoverflow.com/questions/4590229
config.middleware.use Rack::TempfileReaper
# Move secure_headers middleware to after the ShowExceptions
config.middleware.move_after ActionDispatch::ShowExceptions, SecureHeaders::Middleware
# Add lookbook preview paths when enabled
if OpenProject::Configuration.lookbook_enabled?
config.paths.add Primer::ViewComponents::Engine.root.join("app/components").to_s, eager_load: true
+108 -19
View File
@@ -1,25 +1,114 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
# Rails.application.configure do
# config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
#
# # Generate session nonces for permitted importmap, inline scripts, and inline styles.
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src style-src)
#
# # Report violations without enforcing the policy.
# # config.content_security_policy_report_only = true
# end
Rails.application.config.after_initialize do
Rails.application.configure do
config.content_security_policy do |policy|
# Valid for assets
assets_src = ["'self'"]
asset_host = OpenProject::Configuration.rails_asset_host
assets_src << asset_host if asset_host.present?
# Valid for iframes
frame_src = %w['self' https://player.vimeo.com https://www.youtube.com] # rubocop:disable Lint/PercentStringArray
frame_src << OpenProject::Configuration[:security_badge_url]
# Default src
default_src = %w('self') # rubocop:disable Lint/PercentStringArray
# Attachment uploaders
default_src += OpenProject::Configuration.remote_storage_hosts
# Chargebee self-service
chargebee_src = ["https://*.chargebee.com"]
assets_src += chargebee_src
frame_src += chargebee_src
default_src += chargebee_src
# Allow requests to CLI in dev mode
connect_src = default_src + [OpenProject::Configuration.enterprise_trial_creation_host]
# Rules for media (e.g. video sources)
media_src = default_src
media_src << asset_host if asset_host.present?
if OpenProject::Configuration.appsignal_frontend_key
connect_src += ["https://appsignal-endpoint.net"]
end
# Allow connections to S3 for BIM
if OpenProject::Configuration.fog_directory.present?
connect_src += [
"#{OpenProject::Configuration.fog_directory}.s3-#{OpenProject::Configuration.fog_credentials[:region]}.amazonaws.com"
]
end
# Add proxy configuration for Angular CLI to csp
if FrontendAssetHelper.assets_proxied?
proxied = ["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
connect_src += proxied
assets_src += proxied
media_src += proxied
end
# Allow to extend the script-src in specific situations
script_src = assets_src + %w(js.chargebee.com)
# Allow unsafe-eval for rack-mini-profiler
if Rails.env.development? && ENV.fetch("OPENPROJECT_RACK_PROFILER_ENABLED", false)
script_src += %w('unsafe-eval') # rubocop:disable Lint/PercentStringArray
end
# Allow ANDI bookmarklet to run in development mode
# https://www.ssa.gov/accessibility/andi/help/install.html
if Rails.env.development?
script_src += ["https://www.ssa.gov"]
assets_src += ["https://www.ssa.gov"]
end
form_action = default_src
# Allow test s3 bucket for direct uploads in tests
if Rails.env.test?
connect_src += ["test-bucket.s3.amazonaws.com"]
form_action += ["test-bucket.s3.amazonaws.com"]
end
# Configure CSP directives
policy.default_src(*default_src)
policy.base_uri("'self'")
policy.font_src(*assets_src, "data:", "'self'")
policy.form_action(*form_action)
policy.frame_src(*frame_src, "'self'")
policy.frame_ancestors("'self'")
policy.img_src("*", "data:", "blob:")
policy.script_src(*script_src)
policy.script_src_attr("'none'")
policy.style_src(*assets_src, "'unsafe-inline'")
policy.object_src(OpenProject::Configuration[:security_badge_url])
policy.connect_src(*connect_src)
policy.media_src(*media_src)
end
# Generate session nonces for permitted importmap, inline scripts, and inline styles.
# This handles Turbo integration natively
config.content_security_policy_nonce_generator = lambda do |request|
# Use Turbo nonce if available (for Turbo navigation)
if request.env["HTTP_TURBO_REFERRER"].present? && request.env["HTTP_X_TURBO_NONCE"].present?
request.env["HTTP_X_TURBO_NONCE"]
else
# Generate a new nonce based on session
SecureRandom.base64(16)
end
end
config.content_security_policy_nonce_directives = %w(script-src)
end
end
-45
View File
@@ -22,49 +22,4 @@ Rails.application.configure do
# Show notes first, all other panels next
config.lookbook.preview_inspector.drawer_panels = [:notes, "*"]
config.lookbook.ui_theme = "blue"
SecureHeaders::Configuration.named_append(:lookbook) do
proxied =
if FrontendAssetHelper.assets_proxied?
["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
else
[]
end
{
script_src: proxied + %w('unsafe-eval' 'unsafe-inline' 'self'), # rubocop:disable Lint/PercentStringArray
script_src_elem: proxied + %w('unsafe-eval' 'unsafe-inline' 'self'), # rubocop:disable Lint/PercentStringArray
style_src: proxied + %w('self' 'unsafe-inline'), # rubocop:disable Lint/PercentStringArray
style_src_attr: proxied + %w('self' 'unsafe-inline') # rubocop:disable Lint/PercentStringArray
}
end
# rubocop:disable Lint/ConstantDefinitionInBlock
module LookbookCspExtender
extend ActiveSupport::Concern
included do
before_action do
use_content_security_policy_named_append :lookbook
end
end
end
# rubocop:enable Lint/ConstantDefinitionInBlock
Rails.application.reloader.to_prepare do
Lookbook.add_input_type(:octicon, "lookbook/previews/inputs/octicon")
[
Lookbook::ApplicationController,
Lookbook::PreviewController,
Lookbook::PreviewsController,
Lookbook::PageController,
Lookbook::PagesController,
Lookbook::InspectorController,
Lookbook::EmbedsController
].each do |controller|
controller.include LookbookCspExtender
end
end
end
+1 -1
View File
@@ -329,7 +329,7 @@ Redmine::MenuManager.map :admin_menu do |menu|
parent: :admin_work_packages
menu.push :types,
{ controller: "/types" },
{ controller: "/work_package_types/types" },
if: ->(_) { User.current.admin? },
caption: :label_type_plural,
parent: :admin_work_packages
-141
View File
@@ -1,141 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
# rubocop:disable Lint/PercentStringArray
Rails.application.config.after_initialize do
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true,
httponly: true
}
# Let Rails ActionDispatch::SSL middleware handle the Strict-Transport-Security header
config.hsts = SecureHeaders::OPT_OUT
config.x_frame_options = "SAMEORIGIN"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = "origin-when-cross-origin"
# Valid for assets
assets_src = ["'self'"]
asset_host = OpenProject::Configuration.rails_asset_host
assets_src << asset_host if asset_host.present?
# Valid for iframes
frame_src = %w['self' https://player.vimeo.com https://www.youtube.com]
frame_src << OpenProject::Configuration[:security_badge_url]
# Default src
default_src = %w('self')
# Attachment uploaders
default_src += OpenProject::Configuration.remote_storage_hosts
# Chargebee self-service
frame_src += [
"https://js.chargebee.com/",
"#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
]
default_src << "#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
# Allow requests to CLI in dev mode
connect_src = default_src + [OpenProject::Configuration.enterprise_trial_creation_host]
# Rules for media (e.g. video sources)
media_src = default_src
media_src << asset_host if asset_host.present?
if OpenProject::Configuration.appsignal_frontend_key
connect_src += ["https://appsignal-endpoint.net"]
end
# Add proxy configuration for Angular CLI to csp
if FrontendAssetHelper.assets_proxied?
proxied = ["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
connect_src += proxied
assets_src += proxied
media_src += proxied
end
# Allow to extend the script-src in specific situations
script_src = assets_src + %w(js.chargebee.com)
# Allow unsafe-eval for rack-mini-profiler
if Rails.env.development? && ENV.fetch("OPENPROJECT_RACK_PROFILER_ENABLED", false)
script_src += %w('unsafe-eval')
end
# Allow ANDI bookmarklet to run in development mode
# https://www.ssa.gov/accessibility/andi/help/install.html
if Rails.env.development?
script_src += ["https://www.ssa.gov"]
assets_src += ["https://www.ssa.gov"]
end
config.csp = {
preserve_schemes: true,
# Don't append unsafe-inline in CSP as a fallback
disable_nonce_backwards_compatibility: true,
# Fallback when no value is defined
default_src:,
# Allowed uri in <base> tag
base_uri: %w('self'),
# Allow fonts from self, asset host, or DATA uri
font_src: assets_src + %w(data: 'self'),
# Form targets can only be self
form_action: default_src,
# Allow iframe from vimeo (welcome video)
frame_src: frame_src + %w('self'),
frame_ancestors: %w('self'),
# Allow images from anywhere including data urls and blobs (used in resizing)
img_src: %w(* data: blob:),
# Allow scripts from self
script_src:,
script_src_attr: %w('none'),
# Allow unsafe-inline styles
style_src: assets_src + %w('unsafe-inline'),
# Allow object-src from Release API
object_src: [OpenProject::Configuration[:security_badge_url]],
# Connect sources for CLI in dev mode
connect_src:,
# Allow videos from self and from the asset proxy in dev mode.
media_src:
}
end
end
# rubocop:enable Lint/PercentStringArray
+126 -126
View File
@@ -112,57 +112,57 @@ es:
link: "webhook"
scim_clients:
authentication_methods:
sso: "JWT from identity provider"
oauth2_client: "OAuth 2.0 client credentials"
oauth2_token: "Static access token"
sso: "JWT del proveedor de identidad"
oauth2_client: "Credenciales de cliente OAuth 2.0"
oauth2_token: "Token de acceso estático"
created_client_credentials_dialog_component:
title: "Client credentials created"
heading: "Client credentials have been generated"
one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now."
title: "Credenciales del cliente creadas"
heading: "Se han generado las credenciales del cliente"
one_time_hint: "Esta es la única vez que verá el secreto del cliente. Asegúrese de copiarlo ahora."
created_token_dialog_component:
title: "Token created"
heading: "An token has been generated"
title: "Token creado"
heading: "Se ha generado un token"
label_token: "Token"
one_time_hint: "This is the only time you will see this token. Make sure to copy it now."
one_time_hint: "Esta es la única vez que verá este token. Asegúrese de copiarlo ahora."
delete_scim_client_dialog_component:
title: "Delete SCIM client"
heading: "Are you sure you want to delete this SCIM client?"
description: "Users managed by this SCIM client can no longer be updated by it."
title: "Eliminar cliente SCIM"
heading: "¿Seguro que desea eliminar este cliente SCIM?"
description: "Los usuarios gestionados por este cliente SCIM ya no pueden ser actualizados por él."
edit:
label_delete_scim_client: "Delete SCIM client"
label_delete_scim_client: "Eliminar cliente SCIM"
form:
auth_provider_description: "This is the service that users added by the SCIM provider will use to authenticate in OpenProject."
authentication_method_description_html: "This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the <code>scim_v2</code> scope."
description_html: 'Please refer to our <a href="%{docs_url}">documentation on configuring SCIM clients</a> for more information on these configuration options.'
jwt_sub_description_html: 'For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult <a href="%{docs_url}">our documentation</a> to learn how to find the subject claim for your use case.'
name_description: "Choose a name that will help other admins better understand why this client was configured."
auth_provider_description: "Este es el servicio que los usuarios añadidos por el proveedor SCIM utilizarán para autenticarse en OpenProject."
authentication_method_description_html: "Así es como el cliente SCIM se autentica en OpenProject. Asegúrese de que los tokens OAuth incluyen el ámbito <code>scim_v2</code>."
description_html: 'Consulte nuestra <a href="%{docs_url}">documentación sobre la configuración de clientes SCIM</a> para obtener más información sobre estas opciones de configuración.'
jwt_sub_description_html: 'Por ejemplo, para Keycloak, se trata del UUID de la cuenta de servicio asociada al cliente SCIM. Consulte <a href="%{docs_url}">nuestra documentación</a> para saber cómo encontrar la reclamación del asunto para su caso de uso.'
name_description: "Elija un nombre que ayude a otros administradores a entender mejor por qué se configuró este cliente."
index:
description: "SCIM clients configured here are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups."
label_create_button: "Add SCIM client"
description: "Los clientes SCIM configurados aquí pueden interactuar con la API del servidor SCIM de OpenProject para aprovisionar, actualizar y desaprovisionar cuentas y grupos de usuarios."
label_create_button: "Añadir cliente SCIM"
new:
title: "New SCIM client"
title: "Nuevo cliente SCIM"
revoke_static_token_dialog_component:
confirm_button: "Revoke"
title: "Revoke static token"
heading: "Are you sure you want to revoke this token?"
description: "The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API."
confirm_button: "Revocar"
title: "Revocar token estático"
heading: "¿Seguro que desea revocar este token?"
description: "El cliente SCIM que utilice este token ya no podrá acceder a la API del servidor SCIM de OpenProject."
table_component:
blank_slate:
title: "No SCIM clients configured yet"
description: "Add clients to see them here"
user_count: "Users"
title: "Aún no se ha configurado ningún cliente SCIM"
description: "Añada clientes para verlos aquí"
user_count: "Usuarios"
token_list_component:
description: "The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API."
description: "Los tokens que genere aquí pueden ser pasados por un cliente SCIM para acceder a la API SCIM de OpenProject."
heading: "Tokens"
label_add_token: "Token"
label_aria_add_token: "Add token"
label_aria_add_token: "Añadir token"
token_table_component:
blank_slate:
title: "No tokens have been created yet"
description: "You can create one now"
expired: "Expired on %{date}"
revoked: "Revoked on %{date}"
title: "Access token table"
title: "Aún no se ha creado ningún token"
description: "Puede crear uno ahora"
expired: "Caducó el %{date}"
revoked: "Revocado el %{date}"
title: "Tabla de tokens de acceso"
authentication:
login_and_registration: "Inicio de sesión y registro"
announcements:
@@ -327,7 +327,7 @@ es:
admin_only: "Marque esta casilla para que este atributo solo sea visible para los administradores. Los usuarios sin derechos de administrador no podrán verlo ni editarlo."
is_filter: >
Permitir que el campo personalizado se utilice como filtro en las vistas de paquetes de trabajo. Tenga en cuenta que solo 'Para todos los proyectos' seleccionados, el campo personalizado se mostrará en vistas globales.
formula: "Add numeric values or type / to search for an attribute or a mathematical operator."
formula: "Añada valores numéricos o escriba / para buscar un atributo o un operador matemático."
tab:
no_results_title_text: Actualmente no hay campos personalizados.
no_results_content_text: Crear un nuevo campo personalizado
@@ -341,7 +341,7 @@ es:
not_found: "no encontrado."
rules:
copy_workflow_from:
workflow_missing: "has no own workflow."
workflow_missing: "no tiene flujo de trabajo propio."
item:
root_item: "no puede ser un elemento raíz."
not_persisted: "debe ser un elemento ya existente."
@@ -352,8 +352,8 @@ es:
parent:
not_descendant: "debe ser descendiente de la raíz de la jerarquía."
rules:
copy_workflow_from: "Type for workflow copy"
enabled: "Enabled"
copy_workflow_from: "Tipo para la copia del flujo de trabajo"
enabled: "Habilitado"
depth: "Profundidad"
item: "Elemento"
label: "Etiqueta"
@@ -627,11 +627,11 @@ es:
priorities:
edit:
priority_color_text: |
Click to assign or change the color of this priority.
It can be used for highlighting work packages in the table.
Haga clic para asignar o cambiar el color de esta prioridad.
Puede usarse para resaltar paquetes de trabajo en la tabla.
admin:
default:
caption: Making this priority default will override the previous default priority.
caption: Si establece esta prioridad como predeterminada, anulará la prioridad predeterminada anterior.
reactions:
action_title: "Reaccionar"
add_reaction: "Añadir reacción"
@@ -685,9 +685,9 @@ es:
tab: "Configuración del formulario"
projects:
tab: Proyectos
enable_all: Enable for all projects
select_projects: Select projects
select_projects_description: Select the projects in which you would like to use this type.
enable_all: Habilitar en todos los proyectos
select_projects: Seleccionar proyectos
select_projects_description: Seleccione los proyectos en los que desea utilizar este tipo.
settings:
tab: "Ajustes"
type_color_text: El color seleccionado distingue distintos tipos en los diagramas de Gantt o en las tablas de paquetes de trabajo. Por lo tanto, se recomienda usar un color intenso.
@@ -886,7 +886,7 @@ es:
required_description: "Marca este paquete de trabajo como requisito del relacionado"
label_parent_singular: "principal"
label_parent_plural: "principal"
label_other_relations: "Other relations"
label_other_relations: "Otras relaciones"
ghost_relation_title: "Paquete de trabajo relacionado"
ghost_relation_description: "No tiene los permisos necesarios para ver esto."
label_invitation: Invitación
@@ -941,7 +941,7 @@ es:
attribute_name: "Atributo"
help_text: "Texto de ayuda"
auth_provider:
scim_clients: "SCIM clients"
scim_clients: "Clientes SCIM"
capability:
context: "Contexto"
changeset:
@@ -955,7 +955,7 @@ es:
default_value: "Valor predeterminado"
editable: "Editable"
field_format: "Formato"
formula: "Formula"
formula: "Fórmula"
is_filter: "Usado como filtro"
is_for_all: "Para todos los proyectos"
is_required: "Obligatorio"
@@ -984,12 +984,12 @@ es:
enterprise_token:
starts_at: "Válido desde"
subscriber: "Suscriptor"
subscription: "Subscription"
subscription: "Suscripción"
plan: "Plan"
encoded_token: "Token de soporte de Enterprise"
active_user_count_restriction: "Active users"
active_user_count_restriction: "Usuarios activos"
enterprise_trial:
company: "Company"
company: "Empresa"
grids/grid:
page: "Página"
row_count: "Número de filas"
@@ -1107,9 +1107,9 @@ es:
role:
permissions: "Permisos"
scim_client:
auth_provider: "Authentication provider"
authentication_method: "Authentication method"
jwt_sub: "Subject claim"
auth_provider: "Proveedor de autenticación"
authentication_method: "Método de autenticación"
jwt_sub: "Reclamación de asunto"
status:
is_closed: "Paquete de trabajo cerrado"
is_readonly: "Paquete de trabajo de solo lectura"
@@ -1126,7 +1126,7 @@ es:
ongoing: "En curso"
type:
description: "Texto predeterminado para descripción"
attribute_groups: "Form configuration"
attribute_groups: "Configuración del formulario"
is_in_roadmap: "Mostrado en la hoja de ruta de forma predeterminada"
is_default: "Activado para nuevos proyectos de forma predeterminada"
is_milestone: "Es un hito"
@@ -1153,24 +1153,24 @@ es:
password_confirmation: "Confirmación"
consented_at: "Consentido en"
group:
identity_url: "Identity URL"
identity_url: "URL de identidad"
user_preference:
header_look_and_feel: "Look and feel"
header_alerts: "Alerts"
button_update_look_and_feel: "Update look and feel"
button_update_alerts: "Update alerts"
comments_sorting: "Display work package activity sorted by"
disable_keyboard_shortcuts: "Disable keyboard shortcuts"
header_look_and_feel: "Apariencia"
header_alerts: "Alertas"
button_update_look_and_feel: "Actualizar apariencia"
button_update_alerts: "Actualizar alertas"
comments_sorting: "Mostrar la actividad del paquete de trabajo ordenada por"
disable_keyboard_shortcuts: "Deshabilitar atajos de teclado"
disable_keyboard_shortcuts_caption_html: |-
You can choose to disable default <a href="%{href}">keyboard shortcuts</a> if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
Puede optar por deshabilitar los <a href="%{href}">atajos de teclado</a> predeterminados si utiliza un lector de pantalla o desea evitar activar accidentalmente una acción con un atajo.
dismissed_enterprise_banners: "Banners empresariales ocultos"
impaired: "Modo de accesibilidad"
auto_hide_popups: "Automatically hide success banners"
auto_hide_popups_caption: "When enabled, the green success banners will automatically disappear after 5 seconds."
auto_hide_popups: "Ocultar automáticamente los banners de éxito"
auto_hide_popups_caption: "Si se habilitan, los banners verdes de éxito desaparecerán automáticamente al cabo de 5 segundos."
warn_on_leaving_unsaved: "Avisarme cuando salga de un paquete de trabajo con cambios sin guardar"
theme: "Colour mode"
theme: "Modo de color"
time_zone: "Zona horaria"
mode_guideline: "Some modes will overwrite custom theme colours for accessibility and legibility. Please select Light mode for full custom theme support."
mode_guideline: "Algunos modos sobrescribirán los colores de los temas personalizados por motivos de accesibilidad y legibilidad. Seleccione el modo Claro para obtener una compatibilidad total con los temas personalizados."
daily_reminders: "Recordatorios diarios"
workdays: "Días laborables"
version:
@@ -1239,7 +1239,7 @@ es:
error_unauthorized: "no se puede acceder."
error_readonly: "se intentó escribir pero no se puede escribir."
error_conflict: "La información ha sido actualizada por al menos otro usuario mientras tanto."
error_not_found: "not found."
error_not_found: "no encontrado."
email: "no es una dirección de correo válida."
empty: "No puede estar vacío."
enterprise_plan_required: "requiere al menos el plan %{plan_name}."
@@ -1257,7 +1257,7 @@ es:
inclusion: "no está establecido a uno de los valores permitidos."
inclusion_nested: "no está establecido en uno de los valores permitidos en la ruta '%{path}'."
invalid: "no es válido."
invalid_characters: "contains invalid characters."
invalid_characters: "contiene caracteres no válidos."
invalid_url: "no es una URL válida."
invalid_url_scheme: "no es un protocolo admitido (permitidos: %{allowed_schemes})."
less_than_or_equal_to: "debe ser menor o igual a %{count}."
@@ -1300,7 +1300,7 @@ es:
attributes:
content_type:
blank: "El tipo de contenido del archivo no puede estar en blanco."
not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload."
not_allowlisted: "Un filtro automático ha rechazado el archivo. No se permite «%{value}» en la carga."
format: "%{message}"
capability:
context:
@@ -1334,13 +1334,13 @@ es:
scopes:
not_match_configured: "no coincide con los ámbitos disponibles."
enterprise_trial:
already_used: "was already used to create a trial."
failed_to_create: "Trial could not be created (%{status})"
general_consent: "Please accept the terms and conditions."
already_used: "ya se utilizó para crear una prueba."
failed_to_create: "No se ha podido crear la prueba (%{status})"
general_consent: "Acepte los términos y condiciones."
enterprise_token:
only_one_trial: "Only one trial token can be active. Please delete the previous trial token before adding another."
only_one_trial: "Solo puede haber un token de prueba activo. Elimine el token de prueba anterior antes de añadir otro."
unreadable: "no se puede leer. ¿Seguro que es un token de soporte?"
already_added: "This token has already been added."
already_added: "Este token ya se ha añadido."
grids/grid:
overlaps: "superpuesto."
outside: "está fuera de la red."
@@ -1635,8 +1635,8 @@ es:
one: "Rol"
other: "Roles"
scim_client:
one: "SCIM client"
other: "SCIM clients"
one: "Cliente SCIM"
other: "Clientes SCIM"
status: "Estado del paquete de trabajo"
token/api:
one: Token de acceso
@@ -1733,13 +1733,13 @@ es:
derived_start_date: "Fecha de comienzo deseada"
direction: "Dirección"
display_sums: "Mostrar sumas"
domain: "Domain"
domain: "Dominio"
due_date: "Fecha de finalización"
estimated_hours: "Trabajo"
estimated_time: "Trabajo"
email: "Correo electrónico"
entity_type: "Entidad"
expires_at: "Expires on"
expires_at: "Caduca el"
firstname: "Nombre"
filter: "Filtro"
group: "Grupo"
@@ -1788,7 +1788,7 @@ es:
title: "Título"
type: "Tipo"
typeahead: "Autocompletar"
uid: "Unique identifier"
uid: "Identificador único"
updated_at: "Actualizada el"
updated_on: "Actualizada el"
uploader: "Cargador"
@@ -1863,7 +1863,7 @@ es:
button_delete_watcher: "Eliminar observador %{name}"
button_download: "Descargar"
button_duplicate: "Duplicar"
button_duplicate_and_follow: "Duplicate and follow"
button_duplicate_and_follow: "Duplicar y seguir"
button_edit: "Editar"
button_edit_associated_wikipage: "Editar página Wiki asociada: %{page_title}"
button_expand_all: "Expandir todos"
@@ -1902,7 +1902,7 @@ es:
button_unwatch: "No controlar más"
button_update: "Actualizar"
button_upgrade: "Mejorar"
button_buy_now: "Buy now"
button_buy_now: "Comprar ahora"
button_upload: "Cargar"
button_view: "Ver"
button_watch: "Controlar"
@@ -1947,13 +1947,13 @@ es:
text:
failed: 'No se pudo copiar el proyecto "%{source_project_name}" al proyecto "%{target_project_name}".'
succeeded: 'Proyecto copiado de "%{source_project_name}" a "%{target_project_name}".'
source_project_label: "Project copied"
source_project_label: "Proyecto copiado"
copy_options:
dependencies_label: 'Copy from project'
dependencies_label: 'Copiar desde el proyecto'
create_project:
template_label: "Use template"
template_label: "Usar plantilla"
copy_options:
dependencies_label: 'Copy from template'
dependencies_label: 'Copiar desde la plantilla'
create_wiki_page: "Crear una nueva página wiki"
create_wiki_page_button: "Página wiki"
date:
@@ -2128,7 +2128,7 @@ es:
placeholder_users: Usuarios de marcadores de posición
project_list_sharing: Compartición de la lista de proyectos
readonly_work_packages: Paquetes de trabajo de solo lectura
scim_api: SCIM server API
scim_api: API del servidor SCIM
sso_auth_providers: Inicio de sesión único
team_planner_view: Vista del planificador de equipo
virus_scanning: Análisis de antivirus
@@ -2142,9 +2142,9 @@ es:
plan_title: "Extensión Enterprise de %{plan}"
plan_name: "Plan Enterprise %{plan}"
plan_text_html: "Disponible a partir del %{plan_name}."
unlimited: "Unlimited"
unlimited: "Ilimitado"
already_have_token: >
Already have a token? Add it using the button below to upgrade to the booked Enterprise plan.
¿Ya tiene un token? Añádalo utilizando el botón de abajo para actualizar al plan Enterprise reservado.
hide_banner: "Ocultar este banner"
homescreen_description: >
Los planes Enterprise amplían la edición Community de OpenProject que está utilizando actualmente. Incluyen complementos, funciones adicionales y asistencia profesional que permiten a organizaciones de todos los tamaños lograr grandes cosas juntas.
@@ -2152,15 +2152,15 @@ es:
baseline_comparison:
description: Resalte los cambios realizados en esta lista desde cualquier momento en el pasado.
benefits:
description: "What are the benefits of the Enterprise on-premises edition?"
high_security: "Security features"
high_security_text: "Single sign on (SAML, OpenID Connect, CAS), LDAP groups."
installation: "Installation support"
installation_text: "Experienced software engineers guide you through the complete installation and setup process in your own infrastructure."
premium_features: "Enterprise add-ons"
premium_features_text: "Agile boards, custom theme and logo, graphs, intelligent workflows with custom actions, full text search for work package attachments and multi-select custom fields."
professional_support: "Professional support"
professional_support_text: "Get reliable, high-touch support from senior support engineers with expert knowledge about running OpenProject in business-critical environments."
description: "¿Cuáles son las ventajas de Enterprise On-Premises?"
high_security: "Funciones de seguridad"
high_security_text: "Inicio de sesión único (SAML, OpenID Connect, CAS), grupos LDAP."
installation: "Soporte de instalación"
installation_text: "Nuestros experimentados ingenieros de software le guiarán en todo el proceso de instalación y configuración en su propia infraestructura."
premium_features: "Extensiones Enterprise"
premium_features_text: "Tableros Agile, tema y logotipo personalizados, gráficos, flujos de trabajo inteligentes con acciones personalizadas, búsqueda de texto completo en archivos adjuntos de paquetes de trabajo y campos personalizados de selección múltiple."
professional_support: "Soporte profesional"
professional_support_text: "Obtenga ayuda rápida y de confianza de nuestros agentes de soporte con conocimientos especializados sobre cómo ejecutar OpenProject en entornos empresariales críticos."
work_package_subject_generation:
description: "Cree asuntos generados automáticamente utilizando atributos y texto referenciados."
customize_life_cycle:
@@ -2196,8 +2196,8 @@ es:
title: "Inicio de sesión único para almacenamiento en la nube"
description: "Habilite una autenticación fluida y segura para su almacenamiento Nextcloud con el inicio de sesión único. Simplifique la gestión del acceso y mejore la comodidad de los usuarios."
scim_api:
title: "SCIM clients"
description: "Automate user management in OpenProject by seamlessly integrating external identity services like Microsoft Entra or Keycloak through our SCIM Server API. Available starting with the Enterprise corporate plan."
title: "Clientes SCIM"
description: "Automatice la gestión de usuarios en OpenProject integrando a la perfección servicios de identidad externos como Microsoft Entra o Keycloak a través de nuestra API del servidor SCIM. Disponible a partir del plan corporativo Enterprise."
virus_scanning:
description: "Asegúrese de que los archivos cargados en OpenProject se analizan en busca de virus antes de que otros usuarios puedan acceder a ellos."
placeholder_users:
@@ -2212,29 +2212,29 @@ es:
description: " "
teaser:
title:
one: "One day left of %{trial_plan} trial token"
other: "%{count} days left of %{trial_plan} trial token"
description: "You have access to all %{trial_plan} features."
one: "Queda un día del token de prueba de %{trial_plan}"
other: "Quedan %{count} días del token de prueba de %{trial_plan}"
description: "Tiene acceso a todas las funciones de %{trial_plan}."
trial:
not_found: "You have requested a trial token, but that request is no longer available. Please try again."
wait_for_confirmation: "We sent you an email to confirm your address in order to retrieve a trial token."
not_found: "Ha solicitado un token de prueba, pero esa solicitud ya no está disponible. Inténtelo de nuevo."
wait_for_confirmation: "Le hemos enviado un correo electrónico para que confirme su dirección con el fin de recuperar un token de prueba."
already_retrieved: >
Your trial enterprise token was already retrieved. Please check your emails for the token being attached. Please reach out to our support team if you need a new one.
successfully_saved: "Your trial enterprise token has been successfully retrieved."
token_sent: "Trial token requested"
request_again: "Request again"
resend_action: "Resend confirmation email"
welcome_title: "Quick feature overview"
welcome_description: "Get a quick overview of project management and team collaboration with OpenProject Enterprise edition."
Su token de prueba de Enterprise ya se ha recuperado. Busque en sus correos electrónicos el token adjuntado. Póngase en contacto con nuestro equipo de Soporte si necesita uno nuevo.
successfully_saved: "Su token de prueba de Enterprise se ha recuperado correctamente."
token_sent: "Token de prueba solicitado"
request_again: "Solicitar de nuevo"
resend_action: "Reenviar correo electrónico de confirmación"
welcome_title: "Resumen rápido de funciones"
welcome_description: "Obtenga un resumen rápido de la gestión de proyectos y la colaboración en equipo con Enterprise Edition de OpenProject."
confirmation_info: >
We sent you an email on %{date} to %{email} with all the information to start the free trial of OpenProject Enterprise. Please check your inbox and click the confirmation link provided to start your 14-day free trial.
Le enviamos un correo electrónico el %{date} a la dirección %{email} con toda la información para iniciar la prueba gratuita de OpenProject Enterprise. Revise su bandeja de entrada y haga clic en el enlace de confirmación proporcionado para iniciar su prueba gratuita de 14 días.
confirmation_subline: >
Please, check your inbox and follow the steps to start your 14-day free trial.
domain_caption: The token will be valid for your currently configured host name.
Revise su bandeja de entrada y siga los pasos para iniciar su prueba gratuita de 14 días.
domain_caption: El token será válido para su nombre de host configurado actualmente.
receive_newsletter_html: >
I want to receive the OpenProject <a target="_blank" href="https://www.openproject.org/newsletter/">newsletter</a>.
Quiero recibir el <a target="_blank" href="https://www.openproject.org/newsletter/">boletín</a> de OpenProject.
consent_html: >
I agree with the <a target="_blank" href="https://www.openproject.org/terms-of-service/">terms of service</a> and the <a target="_blank" href="https://www.openproject.org/data-privacy-and-security/">privacy policy</a>.
Acepto los <a target="_blank" href="https://www.openproject.org/terms-of-service/">términos del servicio</a> y la <a target="_blank" href="https://www.openproject.org/data-privacy-and-security/">política de privacidad</a>.
enumeration_activities: "Actividades de seguimiento del tiempo"
enumeration_work_package_priorities: "Prioridades del paquete de trabajo"
enumeration_reported_project_statuses: "Estatus del proyecto reportado"
@@ -2694,7 +2694,7 @@ es:
label_select_main_menu_item: Seleccione nuevo elemento de menú principal
label_required_disk_storage: "Almacenamiento en disco requerido"
label_send_invitation: Enviar invitación
label_calculated_value: "Calculated value"
label_calculated_value: "Valor calculado"
label_change_plural: "Cambios"
label_change_properties: "Cambiar propiedades"
label_change_status: "Cambiar estado"
@@ -2830,7 +2830,7 @@ es:
label_follows: "sigue"
label_force_user_language_to_default: "Configurar idioma de usuarios que tienen un lenguaje no permitido por defecto"
label_form_configuration: "Configuración del formato"
label_formula: "Formula"
label_formula: "Fórmula"
label_gantt_chart: "Diagrama de Gantt"
label_gantt_chart_plural: "Diagramas de Gantt"
label_general: "General"
@@ -2867,7 +2867,7 @@ es:
label_information_plural: "Información"
label_installation_guides: "Guías de instalación"
label_integer: "Número entero"
label_interface: "Interface"
label_interface: "Interfaz"
label_internal: "Interno"
label_introduction_video: "Vídeo de introducción"
label_invite_user: "Invitar usuario"
@@ -3228,7 +3228,7 @@ es:
label_work_package_attachments: "Datos adjuntos de paquete de trabajo"
label_work_package_category_new: "Nueva categoría"
label_work_package_category_plural: "Categorías de paquete de trabajo"
label_work_package_comments: "Work package comments"
label_work_package_comments: "Comentarios del paquete de trabajo"
label_work_package_hierarchy: "Jerarquía del paquete de trabajo"
label_work_package_new: "Nuevo paquete de trabajo"
label_work_package_edit: "Editar paquete de trabajo %{name}"
@@ -3569,7 +3569,7 @@ es:
permission_comment_news: "Comentar noticias"
permission_commit_access: "Permiso de escritura sobre el repositorio (commit)"
permission_copy_projects: "Copiar proyectos"
permission_copy_work_packages: "Duplicate work packages"
permission_copy_work_packages: "Duplicar paquetes de trabajo"
permission_create_backup: "Crear copias de seguridad"
permission_delete_work_package_watchers: "Eliminar los observadores"
permission_delete_work_packages: "Eliminar paquetes de trabajo"
@@ -4089,7 +4089,7 @@ es:
phase_gates: "Puertas de fase"
new:
description: "Los cambios en esta fase del proyecto se reflejarán en todos los proyectos en los que esté activada."
heading: "New phase"
heading: "Nueva fase"
both_gate: "Puerta inicial y final"
no_gate: "Sin puerta"
start_gate: "Puerta de inicio"
@@ -4392,7 +4392,7 @@ es:
reminders:
label_remind_at: "Fecha"
note_placeholder: "¿Por qué establece este recordatorio?"
create_success_message: "Reminder set successfully. You will receive a notification for this work package %{reminder_time}."
create_success_message: "Recordatorio establecido correctamente. Recibirá una notificación para este paquete de trabajo %{reminder_time}."
success_update_message: "Recordatorio actualizado con éxito."
success_deletion_message: "Recordatorio eliminado correctamente."
sharing:
@@ -4464,12 +4464,12 @@ es:
message: "Compartir listas de proyectos con usuarios individuales es una extensión Enterprise."
working_days:
info: >
Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level.
Los días no seleccionados se omiten al programar los paquetes de trabajo y ciclos de vida de proyectos (y no se incluyen en el recuento de días). Esto puede anularse a nivel de paquete de trabajo.
instance_wide_info: >
Las fechas añadidas a la lista siguiente se consideran no laborables y se omiten al programar los paquetes de trabajo.
change_button: "Cambiar los días laborables"
warning: >
Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance.
Cambiar qué días de la semana se consideran laborables y cuáles no, puede afectar a las fechas de inicio y fin de todos los paquetes de trabajo y ciclos de vida en todos los proyectos de esta instancia.
journal_note:
changed: _**Días laborales** cambiados (%{changes})._
days:
+2 -1
View File
@@ -642,7 +642,8 @@ de:
title: "Geben Sie Ihr Kennwort ein, um fortzufahren"
pagination:
no_other_page: "Sie befinden sich auf der einzigen Seite."
pagination_controls: "Paginierungs-Steuerung"
page_navigation: "Pagination navigation"
per_page_navigation: 'Items per page selection'
pages:
next: "Nächste Seite"
previous: "Vorherige Seite"
+15 -14
View File
@@ -212,9 +212,10 @@ es:
change_button: "Guardar y reprogramar"
change_title: "Cambiar los días laborables"
removed_title: "Eliminará los siguientes días de la lista de días no laborables:"
change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
change_description: "Cambiar qué días de la semana se consideran laborables y cuáles no, puede afectar a las fechas de inicio y fin de todos los paquetes de trabajo y ciclos de vida en todos los proyectos de esta instancia."
warning: >
Los cambios pueden tardar algún tiempo en surtir efecto. Se le notificará cuando todos los paquetes de trabajo pertinentes se hayan actualizado.
¿Seguro que desea continuar?
work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
Cambiar el modo de cálculo del progreso de basado en el estado a basado en el trabajo hará que el campo <i>% completado</i> se pueda editar libremente. Si introduce opcionalmente valores para <i>Trabajo</i> o <i>Trabajo restante</i>, también se vincularán a <i>% completado</i>. Cambiar <i>Trabajo restante</i> puede entonces actualizar <i>% completado</i>.
@@ -297,7 +298,7 @@ es:
"16_2":
standard:
new_features_html: >
The release contains various new features and improvements, such as <br> <ul class="%{list_styling_class}"> <li>New header and sidebar with improved navigation and design.</li> <li>Smoother experience with Custom fields, Relations, Reminders, Meetings, and My time tracking.</li> <li>Non-latin languages and emojis supported in PDF exports.</li> <li>Option to disable keyboard shortcuts for better accessibility.</li> <li>SCIM server via API (Enterprise add-on).</li> <li>API support for internal comments.</li> </ul>
El lanzamiento contiene varias funciones y mejoras nuevas, como <br>. <ul class="%{list_styling_class}"> <li>Nuevo encabezado y barra lateral con navegación y diseño mejorados.</li> <li>Experiencia mejorada con Campos personalizados, Relaciones, Recordatorios, Reuniones y Seguimiento de mi tiempo.</li> <li>Idiomas no latinos y emojis compatibles en las exportaciones a PDF.</li> <li>Opción de desactivar los atajos de teclado para mejorar la accesibilidad.</li> <li>Servidor SCIM mediante API (extensión Enterprise).</li> <li>Soporte de API para comentarios internos.</li> </ul>
ical_sharing_modal:
title: "Suscribirse al calendario"
inital_setup_error_message: "Se ha producido un error al obtener los datos."
@@ -323,7 +324,7 @@ es:
label_added_time_by: 'Agregado por <a href="%{authorLink}">%{author}</a> a las %{age}'
label_ago: "días antes"
label_all: "todos"
label_all_projects: "All projects"
label_all_projects: "Todos los proyectos"
label_all_uppercase: "Todos"
label_all_work_packages: "todos los paquetes de trabajo"
label_and: "y"
@@ -642,13 +643,13 @@ es:
title: "Confirma tu contraseña para continuar"
pagination:
no_other_page: "Usted está en la única página."
page_navigation: "Pagination navigation"
per_page_navigation: 'Items per page selection'
page_navigation: "Navegación de paginación"
per_page_navigation: 'Selección de elementos por página'
pages:
next: "Next page"
previous: "Previous page"
page_number: Page %{number}
show_per_page: Show %{number} per page
next: "Página siguiente"
previous: "Página anterior"
page_number: Página %{number}
show_per_page: Mostrar %{number} por página
placeholders:
default: "-"
subject: "Escriba aquí el asunto"
@@ -696,7 +697,7 @@ es:
first_day: "Primer día"
last_day: "Último día"
text_are_you_sure: "¿Estás seguro?"
breadcrumb: "Breadcrumb"
breadcrumb: "Ruta de navegación"
text_data_lost: "Todos los datos introducidos se perderán."
text_user_wrote: "%{value} escribió:"
types:
@@ -781,7 +782,7 @@ es:
autocompleter:
placeholder: "Escriba para buscar"
notFoundText: "No se encontraron elementos"
search: "Search"
search: "Buscar"
project:
placeholder: "Seleccionar proyecto"
repositories:
@@ -829,7 +830,7 @@ es:
bulk_actions:
edit: "Edición en masa"
delete: "Eliminación en masa"
duplicate: "Bulk duplicate"
duplicate: "Duplicado en masa"
move: "Cambio masivo del proyecto"
button_clear: "Eliminar"
comment_added: "El comentario fue añadido con éxito."
@@ -875,7 +876,7 @@ es:
header_with_parent: "Nuevo %{type} (Sub-elemento de %{parent_type} #%{id})"
button: "Crear"
duplicate:
title: "Duplicate work package"
title: "Duplicar paquete de trabajo"
hierarchy:
show: "Mostrar modo de jerarquía"
hide: "Ocultar modo de jerarquía"
+5 -4
View File
@@ -297,7 +297,7 @@ pt-BR:
"16_2":
standard:
new_features_html: >
A versão contém vários novos recursos e aprimoramentos, como <br> <ul class="%{list_styling_class}"> <li>Novo cabeçalho e barra lateral com navegação e design aprimorados.</li> <li>Experiência mais suave com campos personalizados, relações, lembretes, reuniões e controle do meu tempo.</li> <li>Suporte a idiomas não latinos e emojis nas exportações de PDF.</li> <li>Opção para desativar os atalhos de teclado para melhorar a acessibilidade.</li> <li>Servidor SCIM via API (complemento Enterprise).</li> <li>Suporte à API para comentários internos.</li> </ul>
A versão inclui vários novos recursos e melhorias, tais como <br> <ul class="%{list_styling_class}"> <li>Novo cabeçalho e barra lateral com navegação e design aprimorados.</li> <li>Experiência mais fluida com Campos personalizados, Relações, Lembretes, Reuniões e Meu acompanhamento de tempo.</li> <li>Suporte a idiomas não latinos e emojis em exportações PDF.</li> <li>Opção para desativar atalhos de teclado para melhor acessibilidade.</li> <li>Servidor SCIM via API (add-on Enterprise).</li> <li>Suporte à API para comentários internos.</li> </ul>
ical_sharing_modal:
title: "Assinar calendário"
inital_setup_error_message: "Ocorreu um erro ao buscar dados."
@@ -642,12 +642,13 @@ pt-BR:
title: "Confirme sua senha para continuar"
pagination:
no_other_page: "Você está na página única."
pagination_controls: "Controles de paginação"
page_navigation: "Pagination navigation"
per_page_navigation: 'Items per page selection'
pages:
next: "Próxima página"
previous: "Página anterior"
page_number: Página %{number}
show_per_page: Mostrar %{number} por página
show_per_page: Exibir %{number} por página
placeholders:
default: "-"
subject: "Informe o assunto aqui"
@@ -695,7 +696,7 @@ pt-BR:
first_day: "Primeiro dia"
last_day: "Último dia"
text_are_you_sure: "Você tem certeza?"
breadcrumb: "Rota de navegação"
breadcrumb: "Trilha de navegação"
text_data_lost: "Todos os dados inseridos serão perdidos."
text_user_wrote: "%{value} escreveu:"
types:
+11 -11
View File
@@ -298,7 +298,7 @@ pt-PT:
"16_2":
standard:
new_features_html: >
The release contains various new features and improvements, such as <br> <ul class="%{list_styling_class}"> <li>New header and sidebar with improved navigation and design.</li> <li>Smoother experience with Custom fields, Relations, Reminders, Meetings, and My time tracking.</li> <li>Non-latin languages and emojis supported in PDF exports.</li> <li>Option to disable keyboard shortcuts for better accessibility.</li> <li>SCIM server via API (Enterprise add-on).</li> <li>API support for internal comments.</li> </ul>
A versão contém várias novas funcionalidades e melhorias, tais como <br> <ul class="%{list_styling_class}"> <li>Novo cabeçalho e barra lateral com navegação e design melhorados.</li> <li>Experiência mais fácil com campos personalizados, relações, lembretes, reuniões e controlo do meu tempo.</li> <li>Línguas não latinas e emojis suportados nas exportações de PDF.</li> <li>Opção para desativar os atalhos de teclado para melhor acessibilidade.</li> <li>Servidor SCIM através de API (complemento Enterprise).</li> <li>Suporte da API para comentários internos.</li> </ul>
ical_sharing_modal:
title: "Subscrever o calendário"
inital_setup_error_message: "Ocorreu um erro ao recuperar os dados."
@@ -324,7 +324,7 @@ pt-PT:
label_added_time_by: 'Adicionado por <a href="%{authorLink}">%{author}</a> a %{age}'
label_ago: "dias atrás"
label_all: "todos"
label_all_projects: "All projects"
label_all_projects: "Todos os projetos"
label_all_uppercase: "Todas"
label_all_work_packages: "todas as tarefas"
label_and: "e"
@@ -643,13 +643,13 @@ pt-PT:
title: "Confirme a sua palavra-passe para continuar"
pagination:
no_other_page: "Mais nenhuma página a apresentar."
page_navigation: "Pagination navigation"
per_page_navigation: 'Items per page selection'
page_navigation: "Navegação por página"
per_page_navigation: 'Seleção de elementos por página'
pages:
next: "Next page"
previous: "Previous page"
page_number: Page %{number}
show_per_page: Show %{number} per page
next: "Próxima página"
previous: "Página anterior"
page_number: Página %{number}
show_per_page: Mostrar %{number} por página
placeholders:
default: "-"
subject: "Introduzir assunto aqui"
@@ -697,7 +697,7 @@ pt-PT:
first_day: "Primeiro dia"
last_day: "Último dia"
text_are_you_sure: "Tem a certeza?"
breadcrumb: "Breadcrumb"
breadcrumb: "Estrutural"
text_data_lost: "Todos os dados inseridos serão perdidos."
text_user_wrote: "%{value} escreveu:"
types:
@@ -830,7 +830,7 @@ pt-PT:
bulk_actions:
edit: "Editar vários"
delete: "Apagar vários"
duplicate: "Bulk duplicate"
duplicate: "Duplicado em massa"
move: "Mudança em massa de projeto"
button_clear: "Limpar"
comment_added: "O comentário foi adicionado com sucesso."
@@ -876,7 +876,7 @@ pt-PT:
header_with_parent: "Nova %{type} (Filho de %{parent_type} #%{id})"
button: "Criar"
duplicate:
title: "Duplicate work package"
title: "Duplicar pacote de trabalho"
hierarchy:
show: "Mostrar modo de hierarquia"
hide: "Ocultar modo de hierarquia"
+4 -4
View File
@@ -644,8 +644,8 @@ ru:
title: "Для продолжения введите свой пароль"
pagination:
no_other_page: "Вы находитесь на единственной странице."
page_navigation: "Pagination navigation"
per_page_navigation: 'Items per page selection'
page_navigation: "Навигация по страницам"
per_page_navigation: 'Количество элементов на страницу'
pages:
next: "Следующая страница"
previous: "Предыдущая страница"
@@ -831,7 +831,7 @@ ru:
bulk_actions:
edit: "Массовое изменение"
delete: "Массовое удаление"
duplicate: "Bulk duplicate"
duplicate: "Групповое дублирование"
move: "Массовое изменение проекта"
button_clear: "Очистить"
comment_added: "Комментарий успешно добавлен."
@@ -877,7 +877,7 @@ ru:
header_with_parent: "Новый %{type} (дочерний пакет работ %{parent_type} № %{id})"
button: "Создать"
duplicate:
title: "Duplicate work package"
title: "Дублировать пакет работ"
hierarchy:
show: "Скрыть режим иерархии"
hide: "Скрыть режим иерархии"
+15 -15
View File
@@ -316,7 +316,7 @@ uk:
label_add_column_before: "Додати стовпець перед"
label_add_columns: "Додати стовпці"
label_add_comment: "Додати коментар"
label_add_comment_title: "Коментар і введіть @, щоб повідомити інших людей"
label_add_comment_title: "Напишіть коментар і введіть @, щоб повідомити інших людей"
label_add_row_after: "Додати рядок після"
label_add_row_before: "Додати рядок перед"
label_add_selected_columns: "Додати вибрані стовпці"
@@ -348,12 +348,12 @@ uk:
label_description: "Опис"
label_details: "Детальніше"
label_display: "Вигляд"
label_cancel_comment: "Скасувати коментар"
label_cancel_comment: "Скасувати додавання коментаря"
label_closed_work_packages: "закрито"
label_collapse: "Згорнути"
label_collapsed: "згорнуто"
label_collapse_all: "Згорнути все"
label_comment: "Коментування"
label_comment: "Коментар"
label_committed_at: "%{committed_revision_link}, %{date}"
label_committed_link: "зафіксовано редакцію %{revision_identifier}"
label_contains: "містить"
@@ -450,14 +450,14 @@ uk:
label_sum: "Сума"
label_sum_for: "Сума для"
label_total_sum: "Загальна сума"
label_subject: "Заголовок"
label_subject: "Тема"
label_this_week: "цього тижня"
label_today: "Сьогодні"
label_time_entry_plural: "Витрачений час"
label_up: "Вгору"
label_user_plural: "Користувачі"
label_activity_show_only_comments: "Показувати заходи лише з коментарями"
label_activity_show_all: "Показати всю активність"
label_activity_show_only_comments: "Показувати лише дії з коментарями"
label_activity_show_all: "Показати всі дії"
label_total_progress: "%{percent}% загального прогресу"
label_total_amount: "Всього: %{amount}"
label_updated_on: "оновлено"
@@ -530,8 +530,8 @@ uk:
drag: "Перевпорядкуйте картки в межах даного списку, перетягуючи їх, або перемістіть їх до іншого списку. Щоб переглянути докладні відомості, натисніть значок інформації (i) у верхньому правому куті або двічі натисніть картку."
wp:
toggler: "Тепер погляньмо на розділ <b>робочих пакетів</b>, де можна дізнатися більше про вашу роботу."
list: "Цей огляд пакета <b>робіт</b> містить список всіх робіт у вашому проєкті, наприклад завдання, віхи, етапи тощо. <br> Пакети робіт можна створювати й редагувати безпосередньо в цьому поданні. Щоб отримати доступ до інформації про певний пакет робіт, просто двічі натисніть його рядок."
full_view: "Подання <b>Деталі пакета робіт</b> містить усю відповідну інформацію, пов’язану з даним пакетом робіт, наприклад його опис, статус, пріоритет, діяльність, залежності й коментарі."
list: "Цей огляд пакета <b>робіт</b> містить список усіх робіт у вашому проєкті, таких як завдання, контрольні точки, етапи тощо. <br> Пакети робіт можна створювати й редагувати безпосередньо в цьому поданні. Щоб отримати доступ до інформації про певний пакет робіт, просто двічі натисніть його рядок."
full_view: "Подання <b>деталей пакета робіт</b> містить усю відповідну інформацію, пов’язану з відповідним пакетом робіт, наприклад його опис, статус, пріоритет, дії, залежності й коментарі."
back_button: "Щоб вийти й повернутися в список пакета робіт, скористайтеся стрілкою повернення у верхньому лівому куті."
create_button: "Кнопка <b>+ Створити</b> додасть новий пакет робіт до вашого проєкту."
gantt_menu: "Легко створюйте розклади й часові шкали проєктів за допомогою модуля «Діаграма Ґанта»."
@@ -835,9 +835,9 @@ uk:
duplicate: "Bulk duplicate"
move: "Групова зміна проєкту"
button_clear: "Очистити"
comment_added: "Коментар був успішно доданий."
comment_added: "Коментар успішно додано."
comment_send_failed: "Сталася помилка. Не вдалося додати коментар."
comment_updated: "Коментар був успішно оновлений."
comment_updated: "Коментар успішно оновлено."
confirm_edit_cancel: "Ви дійсно бажаєте скасувати редагування пакету робіт?"
description_filter: "Фільтр"
description_enter_text: "Введіть текст"
@@ -857,7 +857,7 @@ uk:
message_view_spent_time: "Показувати час, витрачений на цей робочий пакет"
message_work_package_read_only: "Робочий пакет заблоковано в цьому статусі. Атрибут, відмінний від статусу, може бути змінений."
message_work_package_status_blocked: "Пакет робіт недоступний для запису, оскільки призначено закритий статус або закриту версію."
placeholder_filter_by_text: "Тема, опис, коментарі, ..."
placeholder_filter_by_text: "Тема, опис, коментарі"
progress:
title: "Оцінки й прогрес виконання роботи"
baseline:
@@ -894,7 +894,7 @@ uk:
description: Або нічого не було створено, або всі пакети робіт відфільтровані.
limited_results: 'Показати в ручному режимі сортування можна лише стільки робочих пакетів: %{count}. Зменште кількість результатів за допомогою фільтрації або виберіть автоматичне сортування.'
property_groups:
details: "Детальніше"
details: "Деталі"
people: "Люди"
estimatesAndTime: "Оцінки та час"
other: "Інше"
@@ -918,7 +918,7 @@ uk:
responsible: "Відповідальний"
startDate: "Дата початку"
status: "Стан"
subject: "Заголовок"
subject: "Тема"
subproject: "Підпроєкт"
title: "Назва "
type: "Тип"
@@ -930,7 +930,7 @@ uk:
remainingTime: "Залишок роботи"
default_queries:
manually_sorted: "Новий, відсортований уручну запит"
latest_activity: "Останні дії"
latest_activity: "Остання активність"
created_by_me: "Створений мною"
assigned_to_me: "Призначено мені"
recently_created: "Нещодавно створений"
@@ -1028,7 +1028,7 @@ uk:
filter_work_packages_by_relation_type: "Фільтрувати пакети робіт за типом зв’язку"
tabs:
overview: Огляд
activity: Дія
activity: Активність
relations: Зв'язки
watchers: Спостерігачі
files: Файли
+17 -17
View File
@@ -99,12 +99,12 @@ pl:
upgrade: "Zmień plan teraz"
contact: "Skontaktuj się z nami, aby uzyskać demo"
status:
expired: "Expired"
expiring_soon: "Expiring soon"
in_grace_period: "In grace period"
invalid_domain: "Invalid domain"
not_active: "Not active"
trial: "Trial"
expired: "Ważność wygasła"
expiring_soon: "Wkrótce wygasa"
in_grace_period: "W okresie karencji"
invalid_domain: "Nieprawidłowa domena"
not_active: "Nieaktywny"
trial: "Wersja próbna"
jemalloc_allocator: Alokator pamięci Jemalloc
journal_aggregation:
explanation:
@@ -112,21 +112,21 @@ pl:
link: "webhook"
scim_clients:
authentication_methods:
sso: "JWT from identity provider"
oauth2_client: "OAuth 2.0 client credentials"
oauth2_token: "Static access token"
sso: "JWT od dostawcy tożsamości"
oauth2_client: "Poświadczenia klienta OAuth 2.0"
oauth2_token: "Statyczny token dostępu"
created_client_credentials_dialog_component:
title: "Client credentials created"
heading: "Client credentials have been generated"
one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now."
title: "Utworzono poświadczenia klienta"
heading: "Wygenerowano poświadczenia klienta"
one_time_hint: "To jedyny raz, kiedy zobaczysz klucz tajny klienta. Koniecznie skopiuj go teraz."
created_token_dialog_component:
title: "Token created"
heading: "An token has been generated"
title: "Utworzono token"
heading: "Token został wygenerowany"
label_token: "Token"
one_time_hint: "This is the only time you will see this token. Make sure to copy it now."
one_time_hint: "Jest to jedyny raz, gdy zobaczysz ten token. Koniecznie skopiuj go teraz."
delete_scim_client_dialog_component:
title: "Delete SCIM client"
heading: "Are you sure you want to delete this SCIM client?"
title: "Usuń klienta SCIM"
heading: "Czy na pewno chcesz usunąć tego klienta SCIM?"
description: "Users managed by this SCIM client can no longer be updated by it."
edit:
label_delete_scim_client: "Delete SCIM client"
+81 -81
View File
@@ -43,7 +43,7 @@ pt-BR:
commented: "comentou"
internal_comment: Comentário interno
internal_journal: Comentários internos são visíveis apenas para um grupo restrito de membros.
unsaved_changes_confirmation_message: Você tem alterações não salvas. Tem certeza que deseja fechar o editor?
unsaved_changes_confirmation_message: alterações não salvas. Tem certeza de que deseja fechar o editor?
internal_comment_confirmation:
title: "Tornar este comentário público?"
heading: "Tornar este comentário público?"
@@ -100,11 +100,11 @@ pt-BR:
contact: "Contate-nos para uma demonstração"
status:
expired: "Expirado"
expiring_soon: "Expirando em breve"
in_grace_period: "Em período de carência"
expiring_soon: "Prestes a expirar"
in_grace_period: "Dentro do período de carência"
invalid_domain: "Domínio inválido"
not_active: "Inativo"
trial: "Período de avaliação"
trial: "Avaliação"
jemalloc_allocator: Alocador de memória Jemalloc
journal_aggregation:
explanation:
@@ -120,45 +120,45 @@ pt-BR:
heading: "As credenciais do cliente foram geradas"
one_time_hint: "Esta é a única vez que você verá o segredo do cliente. Certifique-se de copiá-lo agora."
created_token_dialog_component:
title: "Novo token criado"
heading: "Uma ficha foi gerada"
label_token: "Ficha"
one_time_hint: "Esta é a única vez que você verá essa ficha, certifique-se de copiá-la agora."
title: "Token criado"
heading: "Uma token foi gerado"
label_token: "Token"
one_time_hint: "Esta é a única vez que você verá este token. Certifique-se de copiá-lo agora."
delete_scim_client_dialog_component:
title: "Excluir cliente SCIM"
heading: "Tem certeza que deseja excluir este cliente SCIM?"
description: "Os usuários gerenciados por este cliente SCIM não podem mais ser atualizados por ele."
heading: "Tem certeza de que deseja excluir este cliente SCIM?"
description: "Usuários gerenciados por este cliente SCIM não poderão mais ser atualizados por ele."
edit:
label_delete_scim_client: "Excluir cliente SCIM"
form:
auth_provider_description: "Este é o serviço que os usuários adicionados pelo provedor SCIM usarão para autenticar no OpenProject."
authentication_method_description_html: "É assim que o cliente SCIM se autentica no OpenProject. Por favor, certifique-se de que as fichas OAuth incluam o escopo <code>scim_v2</code>."
description_html: 'Por favor, consulte nossa <a href="%{docs_url}"> documentação sobre a configuração de clientes SCIM</a> para mais informações sobre essas opções de configuração.'
jwt_sub_description_html: 'Por exemplo, para Keycloak, esse é o UUID da conta de serviço associada com o cliente SCIM. Consulte <a href="%{docs_url}">nossa documentação</a> para saber como encontrar a reivindicação do sujeito para sua utilização.'
name_description: "Escolha um nome que ajudará outros administradores a entender melhor porque esse cliente foi configurado."
auth_provider_description: "Este é o serviço que os usuários adicionados pelo provedor SCIM usarão para autenticar-se no OpenProject."
authentication_method_description_html: "O cliente SCIM autentica-se no OpenProject dessa forma. Verifique se os tokens OAuth contêm o escopo <code>scim_v2</code>."
description_html: 'Consulte nossa <a href="%{docs_url}">documentação sobre configuração de clientes SCIM</a> para obter mais informações sobre essas opções de configuração.'
jwt_sub_description_html: 'Por exemplo, para o Keycloak, este é o UUID da conta de serviço associada ao cliente SCIM. Consulte <a href="%{docs_url}">nossa documentação</a> para saber como encontrar a declaração de sujeito para o seu caso de uso.'
name_description: "Escolha um nome que ajude outros administradores a entender melhor o motivo pelo qual este cliente foi configurado."
index:
description: "Clientes SCIM configurados aqui podem interagir com a API do servidor OpenProject SCIM para provisionar, atualizar e desaprovisionar contas e grupos de usurários."
description: "Clientes SCIM configurados aqui podem interagir com a API do servidor SCIM do OpenProject para provisionar, atualizar e desprovisionar contas de usuários e grupos."
label_create_button: "Adicionar cliente SCIM"
new:
title: "Novo cliente SCIM"
revoke_static_token_dialog_component:
confirm_button: "Revogar"
title: "Revogar token estático"
heading: "Tem certeza que deseja revogar essa ficha?"
description: "Os clientes SCIM que usam esse token não poderão mais acessar a API do servidor SCIM do OpenProject."
heading: "Tem certeza de que deseja revogar este token?"
description: "O cliente SCIM que usa este token não terá mais acesso à API do servidor SCIM do OpenProject."
table_component:
blank_slate:
title: "Nenhum cliente SCIM foi configurado ainda"
title: "Nenhum cliente SCIM configurado até o momento"
description: "Adicione clientes para vê-los aqui"
user_count: "Usuários"
token_list_component:
description: "Os tokens que você gerar aqui podem ser enviados por um cliente SCIM para acessar a API do OPenProject SCIM."
description: "Os tokens gerados aqui podem ser transmitido por um cliente SCIM para acessar a API SCIM do OpenProject."
heading: "Tokens"
label_add_token: "Token"
label_aria_add_token: "Adicionar token"
token_table_component:
blank_slate:
title: "Nenhum token foi criado ainda"
title: "Nenhum token criado até o momento"
description: "Você pode criar um agora"
expired: "Expirado em %{date}"
revoked: "Revogado em %{date}"
@@ -197,9 +197,9 @@ pt-BR:
sync_failed: "Não foi possível sincronizar do LDAP: %{message}."
back_to_index: "Clique aqui para voltar para a lista de conexão."
technical_warning_html: |
Este formulário LDAP requer conhecimento técnico da configuração de seu LDAP / Active Directory.
Este formulário LDAP requer conhecimento técnico da sua configuração LDAP / Active Directory.
<br>
<a href="https://www.openproject.org/help/administration/manage-ldap-authentication/">Por favor, visite a nossa documentação para instruções mais detalhadas</a>.
<a href="https://www.openproject.org/help/administration/manage-ldap-authentication/">Consulte nossa documentação para obter instruções detalhadas</a>.
attribute_texts:
name: Nome arbitrário da conexão LDAP
host: Nome ou endereço IP do host LDAP
@@ -207,7 +207,7 @@ pt-BR:
generic_map: A chave de atributo no LDAP que é mapeada para o atributo `%{attribute}` do OpenProject
admin_map_html: "Opcional: A chave de atributo no LDAP que <strong> se presente </strong> marca o usuário do OpenProject como administrador. Deixe vazio se estiver em dúvida."
system_user_dn_html: |
Digite o DN do usuário do sistema usado para acesso somente leitura.
Informe o DN do usuário do sistema usado para acesso somente leitura.
<br>
Exemplo: uid=openproject,ou=system,dc=example,dc=com
system_user_password: Digite a senha de vinculação do usuário do sistema
@@ -328,7 +328,7 @@ pt-BR:
admin_only: "Marque esta opção para tornar este atributo visível apenas para os administradores. Os usuários sem permissões de administrador não poderão vê-lo ou editá-lo."
is_filter: >
Permita que o campo personalizado seja utilizado num filtro nas visualizações do pacote de trabalho. Note que apenas com a opção "Para todos os projetos" selecionada, o campo personalizado irá aparecer nas visualizações globais.
formula: "Adicione valores numéricos ou digite / para pesquisar por um atributo ou um operador matemático."
formula: "Adicione valores numéricos ou digite / para buscar um atributo ou um operador matemático."
tab:
no_results_title_text: Atualmente, não há campos personalizados.
no_results_content_text: Criar um novo campo personalizado
@@ -353,8 +353,8 @@ pt-BR:
parent:
not_descendant: "deve ser um descendente da raiz da hierarquia."
rules:
copy_workflow_from: "Tipo para cópia do fluxo de trabalho"
enabled: "Ativado"
copy_workflow_from: "Tipo de cópia do fluxo de trabalho"
enabled: "Habilitado"
depth: "Profundidade"
item: "Item"
label: "Rótulo"
@@ -419,7 +419,7 @@ pt-BR:
text: "Esta ação não excluirá nenhum projeto contido na lista. Você tem certeza de que deseja excluir esta lista de projetos?"
settings:
header_details: Detalhes básicos
header_status: Situação do projeto
header_status: Status do projeto
header_relations: Relações do projeto
button_update_details: Atualizar detalhes
button_update_status_description: Atualizar descrição do status
@@ -686,7 +686,7 @@ pt-BR:
tab: Projetos
enable_all: Habilitar para todos os projetos
select_projects: Selecionar projetos
select_projects_description: Selecione os projetos nos quais você gostaria de usar esse tipo.
select_projects_description: Selecione os projetos nos quais deseja usar esse tipo.
settings:
tab: "Configurações"
type_color_text: A cor selecionada distingue os tipos diferentes nas tabelas de Gantt ou tabelas dos pacotes de trabalho. Recomenda-se o uso de uma cor forte.
@@ -707,16 +707,16 @@ pt-BR:
pattern:
label: "Padrão de assunto"
caption_with_supported_attributes_link: Crie padrões adicionando texto, ou digite "/" para pesquisar por %{link}.
insert_as_text: "Nenhum atributo encontrado. Digite um texto como: \"%{word}\""
insert_as_text: "Nenhum atributo encontrado. Adicione como texto: “%{word}"
supported_attributes_link: "atributos compatíveis"
export_configuration:
tab: "Gerar PDF"
intro: "Selecione quais modelos disponíveis você deseja ativar para este tipo. O modelo define o design e os atributos visíveis no PDF exportado de um pacote de trabalho desse tipo. O primeiro modelo da lista é selecionado por padrão."
pdf_export_templates:
label: "Modelos de Exportação PDF"
label: "Modelos de exportação de PDF"
actions:
label_enable_all: "Habilitar todos"
label_disable_all: "Desativar tudo"
label_disable_all: "Desabilitar todos"
versions:
overview:
work_packages_in_archived_projects: "A versão é compartilhada com projetos arquivados que ainda possuem pacotes de trabalho atribuídos a esta versão. Eles são contados, mas não aparecerão nas exibições vinculadas."
@@ -734,20 +734,20 @@ pt-BR:
datepicker_modal:
banner:
description:
automatic_mobile: "Data de início derivada."
click_on_show_relations_to_open_gantt: 'Clique em "%{button_name}" para ter uma visão geral do diagrama de Gantt.'
manual_mobile: "Ignorando relações."
manual_gap_between_predecessors: "Existe uma lacuna entre isto e todos os antecessores."
manual_overlap_with_predecessors: "Sobrepor com pelo menos um antecessor."
manual_with_children: " pacotes de trabalho filhos na hierarquia, mas suas datas de início serão ignoradas."
automatic_mobile: "Data de início calculada."
click_on_show_relations_to_open_gantt: 'Clique em %{button_name} para exibir a visão geral do Gráfico de Gantt.'
manual_mobile: "Ignorar relações."
manual_gap_between_predecessors: "Existe um intervalo entre este item e todos os predecessores."
manual_overlap_with_predecessors: "Sobrepõe-se a pelo menos um predecessor."
manual_with_children: "Este pacote de trabalho possui pacotes de trabalho filho, mas as datas de início deles estão sendo ignoradas."
title:
automatic_mobile: "Agendado automaticamente."
automatic_with_children: "As datas são definidas pelos pacotes de trabalho filhos na hierarquia."
automatic_with_predecessor: "A data de início é definida por um antecessor."
automatic_with_children: "As datas são determinadas pelos pacotes de trabalho filhos."
automatic_with_predecessor: "A data de início é definida por uma tarefa predecessora."
manual_mobile: "Agendado manualmente."
manually_scheduled: "Agendado manualmente. Datas não afetadas pelas relações."
manually_scheduled: "Agendamento manual. Datas não são afetadas por relações."
blankslate:
title: "Nenhum antecessor"
title: "Sem predecessores"
description: "Para ativar o agendamento automático, este pacote de trabalho precisa ter pelo menos um predecessor. Ele será então agendado automaticamente para começar após o predecessor mais próximo."
ignore_non_working_days:
title: "Apenas dias úteis"
@@ -758,20 +758,20 @@ pt-BR:
show_relations: "Exibir relações"
update_inputs_aria_live_message: "Seletor de data atualizado. %{message}"
tabs:
aria_label: "Abas de seletor de datas"
aria_label: "Abas do seletor de data"
children: "Filhos"
dates: "Datas"
predecessors: "Predecessores"
successors: "Sucessores"
blankslate:
predecessors:
title: "Nenhum antecessor"
description: "Este pacote de trabalho não tem antecessores."
title: "Sem predecessores"
description: "Este pacote de trabalho não tem predecessores."
successors:
title: "Nenhum sucessor"
title: "Sem sucessores"
description: "Este pacote de trabalho não tem sucessores."
children:
title: "Sem filhos na hierarquia"
title: "Sem filhos"
description: "Este pacote de trabalho não tem filhos."
x_descendants:
one: "Um pacote de trabalho descendente"
@@ -844,8 +844,8 @@ pt-BR:
follows_description: "O pacote de trabalho relacionado deve ser finalizado antes que este possa ser iniciado"
label_child_singular: "filho"
label_child_plural: "filhos"
new_child: "Novo filho"
new_child_description: "Cria um pacote de trabalho relacionado como um subpacote do pacote de trabalho atual (pai)"
new_child: "Criar novo filho"
new_child_description: "Cria um pacote de trabalho relacionado como subitem do pacote de trabalho atual (pai)"
child: "Filho"
child_description: "Torna o pacote de trabalho relacionado uma subtarefa do pacote de trabalho atual (pai)"
parent: "Pai"
@@ -997,7 +997,7 @@ pt-BR:
widgets: "Widgets"
journal:
notes: "Anotações"
cause_type: "Tipo de Causa"
cause_type: "Tipo de causa"
ldap_auth_source:
account: "Conta"
attr_firstname: "Atributo de nome"
@@ -1014,7 +1014,7 @@ pt-BR:
member:
roles: "Papéis"
notification:
read_ian: "Ler no aplicativo"
read_ian: "Lido no aplicativo"
resource: "Recurso"
oauth_client:
client: "ID do cliente"
@@ -1066,28 +1066,28 @@ pt-BR:
finish_gate: "Controle de fase final"
finish_gate_name: "Nome do controle de fase final"
query:
sums: "Somas"
sums: "Totais"
columns: "Colunas"
column_names: "Colunas"
relations_to_type_column: "Relações com %{type}"
relations_of_type_column: "Relações de %{type}"
child_work_packages: "Pacotes de trabalho filhos"
group_by: "Agrupar resultados por"
sort_by: "Ordenar resultados por"
sort_by: "Classificar resultados por"
filters: "Filtros"
timeline_labels: "Rótulos de linha do tempo"
timeline_visible: "Exibir gráfico de Gantt"
timeline_zoom_level: "Nível de zoom do diagrama de Gantt"
timestamps: "Carimbo de data/hora base da linha de base"
sort_criteria: "Critérios de ordenação"
timeline_zoom_level: "Nível de zoom do gráfico de Gantt"
timestamps: "Carimbos de data/hora da linha de base"
sort_criteria: "Critérios de classificação"
highlighted_attributes: "Atributos destacados"
highlighting_mode: "Modo de destaque"
display_representation: "Modo de exibição"
show_hierarchies: "Modo de exibição"
starred: "Favorito"
hidden: "Ocultado"
manual_sorting: "Ordenação manual"
ordered_work_packages: "Ordem dos pacotes de trabalho"
hidden: "Oculto"
manual_sorting: "Classificação manual"
ordered_work_packages: "Classificação dos pacotes de trabalho"
include_subprojects: "Incluir subprojetos"
results: "Resultados"
relation:
@@ -1109,7 +1109,7 @@ pt-BR:
scim_client:
auth_provider: "Provedor de autenticação"
authentication_method: "Método de autenticação"
jwt_sub: "Reivindicação do sujeito"
jwt_sub: "Declaração de sujeito"
status:
is_closed: "Pacote de trabalho fechado"
is_readonly: "Pacote de trabalho somente leitura"
@@ -1117,7 +1117,7 @@ pt-BR:
default_done_ratio: "% de conclusão"
token/ical:
calendar: "Calendário"
ical_token_query_assignment: "Atribuição da consulta"
ical_token_query_assignment: "Atribuição de consulta"
time_entry:
activity: "Atividade"
hours: "horas"
@@ -1133,7 +1133,7 @@ pt-BR:
color: "Cor"
patterns: "Padrões"
remote_identity:
auth_source: "Fonte de Autenticação"
auth_source: "Fonte de autenticação"
integration: "Integração"
user: "Usuário"
user:
@@ -1145,7 +1145,7 @@ pt-BR:
force_password_change: "Forçar alteração de senha no próximo acesso"
language: "Idioma"
last_login_on: "Último acesso"
failed_login_count: "Tentativas de falhas de acesso"
failed_login_count: "Tentativas de logon com falha"
first_name: "Nome"
last_name: "Sobrenome"
first_login: "Primeiro acesso"
@@ -1155,22 +1155,22 @@ pt-BR:
group:
identity_url: "URL de identidade"
user_preference:
header_look_and_feel: "Aparência e usabilidade"
header_look_and_feel: "Aparência e comportamento"
header_alerts: "Alertas"
button_update_look_and_feel: "Atualizar aparência e usabilidade"
button_update_look_and_feel: "Atualizar aparência e comportamento"
button_update_alerts: "Atualizar alertas"
comments_sorting: "Exibir atividade do pacote de trabalho ordenada por"
comments_sorting: "Exibir atividade do pacote de trabalho classificada por"
disable_keyboard_shortcuts: "Desativar atalhos de teclado"
disable_keyboard_shortcuts_caption_html: |-
Você pode optar por desativar os <a href="%{href}">atalhos de teclado</a> padrão se utilizar um leitor de tela ou quiser evitar acionar uma ação acidentalmente com um atalho
Você pode optar por desativar os <a href="%{href}">atalhos de teclado</a> padrão caso use leitor de tela ou queira evitar acionar ações acidentalmente com atalhos.
dismissed_enterprise_banners: "Banners empresariais ocultos"
impaired: "Modo de acessibilidade"
auto_hide_popups: "Ocultar automaticamente banners concluídos"
auto_hide_popups_caption: "Quando habilitado, os banners de conclusão verdes desaparecerão automaticamente após 5 segundos."
auto_hide_popups: "Ocultar automaticamente as notificações de sucesso"
auto_hide_popups_caption: "Quando habilitado, as notificações verdes de sucesso desaparecem automaticamente após 5 segundos."
warn_on_leaving_unsaved: "Alertar-me ao sair de um pacote de trabalho sem salvar as alterações"
theme: "Modo de cor"
time_zone: "Fuso horário"
mode_guideline: "Alguns modos substituem as cores personalizadas do tema para melhorar a acessibilidade e a legibilidade. Selecione o modo Claro para ter suporte completo a temas personalizados."
mode_guideline: "Alguns modos substituirão as cores personalizadas do tema para melhorar acessibilidade e legibilidade. Selecione o modo Claro para suporte total ao tema personalizado."
daily_reminders: "Lembretes diários"
workdays: "Dias úteis"
version:
@@ -1335,10 +1335,10 @@ pt-BR:
not_match_configured: "não corresponde a escopos disponíveis."
enterprise_trial:
already_used: "já foi usado para criar uma avaliação."
failed_to_create: "A avaliação não pôde ser criada (%{status})"
general_consent: "Por favor, aceite os termos e condições."
failed_to_create: "Não foi possível criar a avaliação (%{status})"
general_consent: "Aceite os termos e condições."
enterprise_token:
only_one_trial: "Apenas um token de avaliação pode estar ativo. Por favor, exclua o token de avaliação anterior antes de adicionar outro."
only_one_trial: "Apenas um token de avaliação pode estar ativo. Exclua o token de avaliação anterior antes de adicionar outro."
unreadable: "não pode ser lido. Tem certeza que é um token de suporte?"
already_added: "Este token já foi adicionado."
grids/grid:
@@ -1697,7 +1697,7 @@ pt-BR:
added_date: "definido para %{date}"
changed_date: "alterado de %{from} para %{to}"
deactivated: "desativado"
deleted_project_phase: "Excluir fase de projeto"
deleted_project_phase: "Fase do projeto excluída"
phase_and_both_gates: "%{phase_message}. %{start_gate_message} e %{finish_gate_message}"
phase_and_one_gate: "%{phase_message}. %{gate_message}"
removed_date: "data excluída %{date}"
@@ -1710,7 +1710,7 @@ pt-BR:
actor: "Ator"
action: "Ação"
author: "Autor"
avatar: "Imagem do perfil"
avatar: "Avatar"
base: "Erro geral:"
body: "Mensagem"
blocks_ids: "IDs dos pacotes de trabalho bloqueados"
@@ -1725,7 +1725,7 @@ pt-BR:
custom_options: "Valores possíveis"
custom_values: "Campos personalizados"
date: "Data"
dates_interval: "Período"
dates_interval: "Intervalo de datas"
default_columns: "Colunas padrão"
description: "Descrição"
derived_due_date: "Data de término derivada"
@@ -1760,10 +1760,10 @@ pt-BR:
mail: "E-mail"
name: "Nome"
note: "Nota"
notes: "Anotações"
notes: "Observações"
number: "Número"
options: "Opções"
operator: "Operador(a)"
operator: "Operador"
password: "Senha"
priority: "Prioridade"
project: "Projeto"
@@ -1781,7 +1781,7 @@ pt-BR:
status: "Status"
state: "Estado"
subject: "Assunto"
slug: "Link encurtado"
slug: "Identificador simples"
summary: "Sumário"
time_zone: "Fuso horário"
text: "Texto"
@@ -1951,7 +1951,7 @@ pt-BR:
copy_options:
dependencies_label: 'Copiar do projeto'
create_project:
template_label: "Utilizar modelo"
template_label: "Usar modelo"
copy_options:
dependencies_label: 'Copiar do modelo'
create_wiki_page: "Criar nova página wiki"
@@ -2197,7 +2197,7 @@ pt-BR:
description: "Ative a autenticação segura e contínua para o seu armazenamento Nextcloud com Autenticação única. Simplifique o gerenciamento de acessos e melhore a experiência do usuário."
scim_api:
title: "Clientes SCIM"
description: "Automatize o gerenciamento de usuários no OpenProject integrando de forma suave os serviços de identidade externos como Microsoft Entra ou Keycloak através da nossa API do servidor SCIM. Disponível começando com o Plano corporativo empresarial."
description: "Automatize o gerenciamento de usuários no OpenProject integrando de forma contínua serviços de identidade externos como Microsoft Entra ou Keycloak por meio da nossa API SCIM Server. Disponível a partir do plano corporativo Enterprise."
virus_scanning:
description: "Garantir que os arquivos carregados no OpenProject sejam verificados quanto à presença de vírus antes de serem acessados por outros usuários."
placeholder_users:
+98 -98
View File
@@ -101,7 +101,7 @@ pt-PT:
status:
expired: "Expirado"
expiring_soon: "Expira em breve"
in_grace_period: "In grace period"
in_grace_period: "Em período de carência"
invalid_domain: "Domínio inválido"
not_active: "Não ativo"
trial: "Teste"
@@ -112,57 +112,57 @@ pt-PT:
link: "webhook"
scim_clients:
authentication_methods:
sso: "JWT from identity provider"
oauth2_client: "OAuth 2.0 client credentials"
oauth2_token: "Static access token"
sso: "JWT do fornecedor de identidade"
oauth2_client: "Credenciais do cliente OAuth 2.0"
oauth2_token: "Token de acesso estático"
created_client_credentials_dialog_component:
title: "Client credentials created"
heading: "Client credentials have been generated"
one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now."
title: "Credenciais de cliente criadas"
heading: "As credenciais do cliente foram geradas"
one_time_hint: "Esta é a única vez que verá o segredo do cliente. Não se esqueça de o copiar agora."
created_token_dialog_component:
title: "Token created"
heading: "An token has been generated"
title: "Token criado"
heading: "Foi gerado um token"
label_token: "Token"
one_time_hint: "This is the only time you will see this token. Make sure to copy it now."
one_time_hint: "Esta é a única vez que verá este token. Não se esqueça de o copiar agora."
delete_scim_client_dialog_component:
title: "Delete SCIM client"
heading: "Are you sure you want to delete this SCIM client?"
description: "Users managed by this SCIM client can no longer be updated by it."
title: "Eliminar o cliente SCIM"
heading: "Tem a certeza de que quer eliminar este cliente SCIM?"
description: "Os utilizadores geridos por este cliente SCIM já não podem ser atualizados por ele."
edit:
label_delete_scim_client: "Delete SCIM client"
label_delete_scim_client: "Eliminar o cliente SCIM"
form:
auth_provider_description: "This is the service that users added by the SCIM provider will use to authenticate in OpenProject."
authentication_method_description_html: "This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the <code>scim_v2</code> scope."
description_html: 'Please refer to our <a href="%{docs_url}">documentation on configuring SCIM clients</a> for more information on these configuration options.'
jwt_sub_description_html: 'For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult <a href="%{docs_url}">our documentation</a> to learn how to find the subject claim for your use case.'
name_description: "Choose a name that will help other admins better understand why this client was configured."
auth_provider_description: "Este é o serviço que os utilizadores adicionados pelo fornecedor SCIM utilizarão para se autenticarem no OpenProject."
authentication_method_description_html: "É assim que o cliente SCIM se autentica no OpenProject. Garanta que os tokens OAuth incluem o âmbito <code>scim_v2</code>."
description_html: 'Consulte a nossa <a href="%{docs_url}">documentação sobre a configuração de clientes SCIM</a> para mais informações sobre estas opções de configuração.'
jwt_sub_description_html: 'Por exemplo, para o Keycloak, este é o UUID da conta de serviço associada ao cliente SCIM. Consulte <a href="%{docs_url}">a nossa documentação</a> para saber como encontrar a declaração de assunto para o seu caso de utilização.'
name_description: "Escolha um nome que ajude os outros administradores a perceber melhor porque é que este cliente foi configurado."
index:
description: "SCIM clients configured here are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups."
label_create_button: "Add SCIM client"
description: "Os clientes SCIM aqui configurados conseguem interagir com a API do servidor OpenProject SCIM para prover, atualizar e desprover contas e grupos de utilizadores."
label_create_button: "Adicionar cliente SCIM"
new:
title: "New SCIM client"
title: "Novo cliente SCIM"
revoke_static_token_dialog_component:
confirm_button: "Revoke"
title: "Revoke static token"
heading: "Are you sure you want to revoke this token?"
description: "The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API."
confirm_button: "Revogar"
title: "Revogar token estático"
heading: "Tem a certeza de que deseja revogar este token?"
description: "O cliente SCIM que utiliza este token deixará de poder aceder à API do servidor SCIM do OpenProject."
table_component:
blank_slate:
title: "No SCIM clients configured yet"
description: "Add clients to see them here"
user_count: "Users"
title: "Ainda não há clientes SCIM configurados"
description: "Adicione clientes para os ver aqui"
user_count: "Utilizadores"
token_list_component:
description: "The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API."
description: "Os tokens que gerar aqui podem ser passados por um cliente SCIM para aceder à API do OpenProject SCIM."
heading: "Tokens"
label_add_token: "Token"
label_aria_add_token: "Add token"
label_aria_add_token: "Adicionar token"
token_table_component:
blank_slate:
title: "No tokens have been created yet"
description: "You can create one now"
expired: "Expired on %{date}"
revoked: "Revoked on %{date}"
title: "Access token table"
title: "Ainda não foram criados tokens"
description: "Pode criar um agora"
expired: "Expirou em %{date}"
revoked: "Revogado em %{date}"
title: "Tabela de tokens de acesso"
authentication:
login_and_registration: "Iniciar sessão e registo"
announcements:
@@ -327,7 +327,7 @@ pt-PT:
admin_only: "Selecione para tornar este atributo visível apenas para os administradores. Os utilizadores sem direitos de administrador não o poderão ver ou editar."
is_filter: >
Permita que o campo personalizado seja utilizado num filtro nas vistas do pacote de trabalho. Note que apenas com a opção "Para todos os projetos" selecionada, o campo personalizado irá aparecer nas vistas globais.
formula: "Add numeric values or type / to search for an attribute or a mathematical operator."
formula: "Adicione valores numéricos ou escreva / para procurar um atributo ou um operador matemático."
tab:
no_results_title_text: Atualmente, não existem campos personalizados.
no_results_content_text: Criar um novo campo personalizado
@@ -352,7 +352,7 @@ pt-PT:
parent:
not_descendant: "tem de ser um descendente da raiz da hierarquia."
rules:
copy_workflow_from: "Type for workflow copy"
copy_workflow_from: "Tipo de cópia do fluxo de trabalho"
enabled: "Ativado"
depth: "Profundidade"
item: "Item"
@@ -684,9 +684,9 @@ pt-PT:
tab: "Configuração do formulário"
projects:
tab: Projetos
enable_all: Enable for all projects
select_projects: Select projects
select_projects_description: Select the projects in which you would like to use this type.
enable_all: Ativar para todos os projetos
select_projects: Selecionar projetos
select_projects_description: Selecione os projetos em que pretende utilizar este tipo.
settings:
tab: "Definições"
type_color_text: A cor selecionada distingue diferentes tipos em gráficos de Gantt ou tabelas de pacotes de trabalho. Portanto é recomendado usar uma cor forte.
@@ -941,7 +941,7 @@ pt-PT:
attribute_name: "Atributo"
help_text: "Texto de ajuda"
auth_provider:
scim_clients: "SCIM clients"
scim_clients: "Clientes SCIM"
capability:
context: "Contexto"
changeset:
@@ -955,7 +955,7 @@ pt-PT:
default_value: "Valor predefinido"
editable: "Editável"
field_format: "Formato"
formula: "Formula"
formula: "Fórmula"
is_filter: "Usado como filtro"
is_for_all: "Para todos os projetos"
is_required: "Obrigatório"
@@ -1107,9 +1107,9 @@ pt-PT:
role:
permissions: "Permissões"
scim_client:
auth_provider: "Authentication provider"
authentication_method: "Authentication method"
jwt_sub: "Subject claim"
auth_provider: "Fornecedor de autenticação"
authentication_method: "Método de autenticação"
jwt_sub: "Reivindicação do sujeito"
status:
is_closed: "Pacote de trabalho fechado"
is_readonly: "Pacote de trabalho só de leitura"
@@ -1126,7 +1126,7 @@ pt-PT:
ongoing: "Em curso"
type:
description: "Texto padrão para a descrição"
attribute_groups: "Form configuration"
attribute_groups: "Configuração do formulário"
is_in_roadmap: "Apresentado no roteiro por defeito"
is_default: "Ativado para novos projetos por padrão"
is_milestone: "É um marco"
@@ -1153,20 +1153,20 @@ pt-PT:
password_confirmation: "Confirmação"
consented_at: "Consentiu em"
group:
identity_url: "Identity URL"
identity_url: "URL de identidade"
user_preference:
header_look_and_feel: "Look and feel"
header_look_and_feel: "Aspeto e sensação"
header_alerts: "Alertas"
button_update_look_and_feel: "Update look and feel"
button_update_look_and_feel: "Atualizar o aspeto e a sensação"
button_update_alerts: "Atualizar alertas"
comments_sorting: "Exibir a atividade do pacote de trabalho ordenada por"
disable_keyboard_shortcuts: "Desativar atalhos de teclado"
disable_keyboard_shortcuts_caption_html: |-
You can choose to disable default <a href="%{href}">keyboard shortcuts</a> if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
Pode optar por desativar <a href="%{href}">atalhos de teclado</a> predefinidos se utilizar um leitor de ecrã, ou se quiser evitar desencadear acidentalmente uma ação com um atalho.
dismissed_enterprise_banners: "Faixas de empresas ocultas"
impaired: "Modo de acessibilidade"
auto_hide_popups: "Automatically hide success banners"
auto_hide_popups_caption: "When enabled, the green success banners will automatically disappear after 5 seconds."
auto_hide_popups: "Ocultar automaticamente os banners de sucesso"
auto_hide_popups_caption: "Quando ativado, os banners verdes de sucesso desaparecerão automaticamente após 5 segundos."
warn_on_leaving_unsaved: "Alertar-me quando sair de um pacote de trabalho sem guardar as alterações"
theme: "Modo de cor"
time_zone: "Fuso horário"
@@ -1239,7 +1239,7 @@ pt-PT:
error_unauthorized: "não pode ser acedido."
error_readonly: "tentou escrever, mas não é gravável."
error_conflict: "A informação foi atualizada entretanto por pelo menos por um outro utilizador."
error_not_found: "not found."
error_not_found: "não encontrado."
email: "não é um endereço de correio válido."
empty: "não pode estar em branco."
enterprise_plan_required: "requer pelo menos o %{plan_name}."
@@ -1257,7 +1257,7 @@ pt-PT:
inclusion: "não está definido como um dos valores permitidos."
inclusion_nested: "não está definido como um dos valores permitidos no caminho '%{path}'."
invalid: "é inválido."
invalid_characters: "contains invalid characters."
invalid_characters: "contém caracteres inválidos."
invalid_url: "não é um URL válido."
invalid_url_scheme: "não é um protocolo suportado (permitido: %{allowed_schemes})."
less_than_or_equal_to: "deve ser menor ou igual a %{count}."
@@ -1635,8 +1635,8 @@ pt-PT:
one: "Função"
other: "Papel"
scim_client:
one: "SCIM client"
other: "SCIM clients"
one: "Cliente SCIM"
other: "Clientes SCIM"
status: "Estado do pacote de trabalho"
token/api:
one: Token de acesso
@@ -1739,7 +1739,7 @@ pt-PT:
estimated_time: "Trabalho"
email: "E-mail"
entity_type: "Entidade"
expires_at: "Expires on"
expires_at: "Expira em"
firstname: "Primeiro nome"
filter: "Filtro"
group: "Grupo"
@@ -1788,7 +1788,7 @@ pt-PT:
title: "Título"
type: "Tipo"
typeahead: "Preenchimento automático"
uid: "Unique identifier"
uid: "Identificador único"
updated_at: "Atualizado em"
updated_on: "Atualizado em"
uploader: "Enviado por"
@@ -1863,7 +1863,7 @@ pt-PT:
button_delete_watcher: "Eliminar observador %{name}"
button_download: "Transferir"
button_duplicate: "Duplicar"
button_duplicate_and_follow: "Duplicate and follow"
button_duplicate_and_follow: "Duplicar e seguir"
button_edit: "Editar"
button_edit_associated_wikipage: "Editar página Wiki associada: %{page_title}"
button_expand_all: "Expandir Tudo"
@@ -1947,13 +1947,13 @@ pt-PT:
text:
failed: 'Não foi possível copiar o projecto "%{source_project_name}" para o projecto "%{target_project_name}".'
succeeded: 'Projecto "%{source_project_name}" copiado para "%{target_project_name}".'
source_project_label: "Project copied"
source_project_label: "Projeto copiado"
copy_options:
dependencies_label: 'Copy from project'
dependencies_label: 'Copiar do projeto'
create_project:
template_label: "Use template"
template_label: "Utilizar modelo"
copy_options:
dependencies_label: 'Copy from template'
dependencies_label: 'Copiar do modelo'
create_wiki_page: "Criar nova página wiki"
create_wiki_page_button: "Página wiki"
date:
@@ -2071,7 +2071,7 @@ pt-PT:
units:
minute_abbreviated:
one: "min"
other: "mins"
other: "min"
hour:
one: "hora"
other: "horas"
@@ -2128,7 +2128,7 @@ pt-PT:
placeholder_users: Utilizadores de marcador de posição
project_list_sharing: Partilha da lista de projetos
readonly_work_packages: Pacotes de trabalho só de leitura
scim_api: SCIM server API
scim_api: API do servidor SCIM
sso_auth_providers: Início de sessão único
team_planner_view: Vista do planeador de equipas
virus_scanning: Análise antivírus
@@ -2144,7 +2144,7 @@ pt-PT:
plan_text_html: "Disponível a partir do %{plan_name}."
unlimited: "Ilimitado"
already_have_token: >
Already have a token? Add it using the button below to upgrade to the booked Enterprise plan.
Já tem um token? Adicione-o com o botão abaixo para atualizar para o plano Enterprise reservado.
hide_banner: "Ocultar esta faixa"
homescreen_description: >
Os planos Enterprise aumentam a Edição Community do OpenProject que está atualmente a usar. Eles incluem complementos, funcionalidades adicionais e assistência profissional que permite que organizações de todas as dimensões alcancem grandes coisas em conjunto.
@@ -2152,15 +2152,15 @@ pt-PT:
baseline_comparison:
description: Destacar as alterações introduzidas nesta lista desde qualquer ponto no passado.
benefits:
description: "What are the benefits of the Enterprise on-premises edition?"
description: "Quais são os benefícios da edição Empresarial no local?"
high_security: "Funcionalidades de segurança"
high_security_text: "Single sign on (SAML, OpenID Connect, CAS), LDAP groups."
high_security_text: "Início de sessão único (SAML, OpenID Connect, CAS), grupos LDAP."
installation: "Apoio à instalação"
installation_text: "Engenheiros de software experientes darão orientações durante toda a instalação e processo de configuração na sua própria infraestrutura."
premium_features: "Enterprise add-ons"
premium_features_text: "Agile boards, custom theme and logo, graphs, intelligent workflows with custom actions, full text search for work package attachments and multi-select custom fields."
professional_support: "Professional support"
professional_support_text: "Get reliable, high-touch support from senior support engineers with expert knowledge about running OpenProject in business-critical environments."
premium_features: "Complementos Enterprise"
premium_features_text: "Agile boards, gráficos de temas e logótipos personalizados, fluxos de trabalho inteligentes com ações personalizadas, pesquisa de texto completo para anexos em pacotes de trabalho, e campos personalizados de multi-seleção."
professional_support: "Apoio profissional"
professional_support_text: "Receba apoio fiável e de alto nível de engenheiros séniores de suporte, com conhecimento especializado sobre a gestão do OpenProject em ambientes de negócios críticos."
work_package_subject_generation:
description: "Crie temas gerados automaticamente utilizando atributos e texto referenciados."
customize_life_cycle:
@@ -2196,8 +2196,8 @@ pt-PT:
title: "Início de sessão único para armazenamento Nextcloud"
description: "Permita uma autenticação perfeita e segura para o seu armazenamento Nextcloud com o início de sessão único. Simplifique a gestão do acesso e aumente a conveniência do utilizador."
scim_api:
title: "SCIM clients"
description: "Automate user management in OpenProject by seamlessly integrating external identity services like Microsoft Entra or Keycloak through our SCIM Server API. Available starting with the Enterprise corporate plan."
title: "Clientes SCIM"
description: "Automatize a gestão de utilizadores no OpenProject, integrando na perfeição serviços de identidade externos, como Microsoft Entra ou Keycloak, através da nossa API do Servidor SCIM. Disponível a partir do plano Enterprise empresarial."
virus_scanning:
description: "Assegure-se que os ficheiros carregados no OpenProject são verificados quanto à presença de vírus antes de serem acessíveis a outros utilizadores."
placeholder_users:
@@ -2212,29 +2212,29 @@ pt-PT:
description: " "
teaser:
title:
one: "One day left of %{trial_plan} trial token"
other: "%{count} days left of %{trial_plan} trial token"
description: "You have access to all %{trial_plan} features."
one: "Um dia restante do token de teste %{trial_plan}"
other: "F%{count} dias restantes do token de teste %{trial_plan}"
description: "Tem acesso a todas as funcionalidades de %{trial_plan}."
trial:
not_found: "You have requested a trial token, but that request is no longer available. Please try again."
wait_for_confirmation: "We sent you an email to confirm your address in order to retrieve a trial token."
not_found: "Solicitou um token de teste, mas esse pedido já não está disponível. Tente novamente."
wait_for_confirmation: "Enviámos um e-mail para confirmar o seu endereço, a fim de obter um token de avaliação."
already_retrieved: >
Your trial enterprise token was already retrieved. Please check your emails for the token being attached. Please reach out to our support team if you need a new one.
successfully_saved: "Your trial enterprise token has been successfully retrieved."
token_sent: "Trial token requested"
request_again: "Request again"
resend_action: "Resend confirmation email"
welcome_title: "Quick feature overview"
welcome_description: "Get a quick overview of project management and team collaboration with OpenProject Enterprise edition."
O seu token de teste Enterprise já foi recuperado. Verifique os seus e-mails para ver se o token foi anexado. Contacte a nossa equipa de apoio se precisar de um novo token.
successfully_saved: "O seu token de teste Enterprise foi recuperado com êxito."
token_sent: "Token de teste solicitado"
request_again: "Solicitar novamente"
resend_action: "Reenviar e-mail de confirmação"
welcome_title: "Síntese rápida das funcionalidades"
welcome_description: "Obter uma síntese da gestão do projeto e colaboração em equipa com a edição OpenProject Enterprise."
confirmation_info: >
We sent you an email on %{date} to %{email} with all the information to start the free trial of OpenProject Enterprise. Please check your inbox and click the confirmation link provided to start your 14-day free trial.
Enviámos-lhe um e-mail para %{date} para %{email} com todas as informações para iniciar o teste gratuito do OpenProject Enterprise. Verifique a sua caixa de entrada e clique na ligação de confirmação fornecida para iniciar o teste gratuito de 14 dias.
confirmation_subline: >
Please, check your inbox and follow the steps to start your 14-day free trial.
domain_caption: The token will be valid for your currently configured host name.
Verifique a sua caixa de correio eletrónico e siga os passos para iniciar o seu teste gratuito de 14 dias.
domain_caption: O token será válido para o nome do anfitrião configurado atualmente.
receive_newsletter_html: >
I want to receive the OpenProject <a target="_blank" href="https://www.openproject.org/newsletter/">newsletter</a>.
Quero receber a <a target="_blank" href="https://www.openproject.org/newsletter/">newsletter</a> do OpenProject.
consent_html: >
I agree with the <a target="_blank" href="https://www.openproject.org/terms-of-service/">terms of service</a> and the <a target="_blank" href="https://www.openproject.org/data-privacy-and-security/">privacy policy</a>.
Concordo com os <a target="_blank" href="https://www.openproject.org/terms-of-service/">termos de serviço</a> e a <a target="_blank" href="https://www.openproject.org/data-privacy-and-security/">política de privacidade</a>.
enumeration_activities: "Atividades de controlo de tempo"
enumeration_work_package_priorities: "Prioridades do pacote de trabalho"
enumeration_reported_project_statuses: "Estado do projeto reportado"
@@ -2694,7 +2694,7 @@ pt-PT:
label_select_main_menu_item: Seleccione novo item do menu principal
label_required_disk_storage: "Espaço em disco necessário"
label_send_invitation: Enviar convite
label_calculated_value: "Calculated value"
label_calculated_value: "Valor calculado"
label_change_plural: "Alterações"
label_change_properties: "Alterar propriedades"
label_change_status: "Alterar estado"
@@ -2830,7 +2830,7 @@ pt-PT:
label_follows: "segue"
label_force_user_language_to_default: "Idioma de utilizadores definida com uma linguagem não permitida como padrão"
label_form_configuration: "Configuração do formulário"
label_formula: "Formula"
label_formula: "Fórmula"
label_gantt_chart: "Gráfico de Gantt"
label_gantt_chart_plural: "Gráficos de Gantt"
label_general: "Geral"
@@ -3228,7 +3228,7 @@ pt-PT:
label_work_package_attachments: "Anexos do pacote de trabalho"
label_work_package_category_new: "Nova categoria"
label_work_package_category_plural: "Categorias de pacotes de trabalho"
label_work_package_comments: "Work package comments"
label_work_package_comments: "Comentários do pacote de trabalho"
label_work_package_hierarchy: "Hierarquia de pacotes de trabalho"
label_work_package_new: "Novo pacote de trabalho"
label_work_package_edit: "Editar pacote de trabalho %{name}"
@@ -3569,7 +3569,7 @@ pt-PT:
permission_comment_news: "Comentar notícias"
permission_commit_access: "Acesso de leitura/escrita ao repositório (comprometer)"
permission_copy_projects: "Copiar projeto"
permission_copy_work_packages: "Duplicate work packages"
permission_copy_work_packages: "Duplicar pacotes de trabalho"
permission_create_backup: "Criar cópias de segurança"
permission_delete_work_package_watchers: "Eliminar observador"
permission_delete_work_packages: "Eliminar pacotes de trabalho"
@@ -4087,7 +4087,7 @@ pt-PT:
phase_gates: "Portas de fases"
new:
description: "As alterações a esta fase do projeto serão refletidas em todos os projetos em que estiver ativada."
heading: "New phase"
heading: "Nova fase"
both_gate: "Porta de início e conclusão"
no_gate: "Sem porta"
start_gate: "Porta de início"
@@ -4391,7 +4391,7 @@ pt-PT:
reminders:
label_remind_at: "Data"
note_placeholder: "Porque está a definir este lembrete?"
create_success_message: "Reminder set successfully. You will receive a notification for this work package %{reminder_time}."
create_success_message: "Lembrete definido com sucesso. Irá receber uma notificação para este pacote de trabalho %{reminder_time}."
success_update_message: "Lembrete atualizado com sucesso."
success_deletion_message: "Lembrete eliminado com sucesso."
sharing:
+3 -3
View File
@@ -1784,7 +1784,7 @@ ru:
estimated_time: "Предполагаемое время"
email: "Электронная почта"
entity_type: "Тип сущности"
expires_at: "Expires on"
expires_at: "Истекает"
firstname: "Имя"
filter: "Фильтр"
group: "Группа"
@@ -1908,7 +1908,7 @@ ru:
button_delete_watcher: "Удаление наблюдателя %{name}"
button_download: "Скачать"
button_duplicate: "Дублировать"
button_duplicate_and_follow: "Duplicate and follow"
button_duplicate_and_follow: "Дублировать и следить"
button_edit: "Правка"
button_edit_associated_wikipage: "Редактирование связанной wiki-страницы: %{page_title}"
button_expand_all: "Развернуть все"
@@ -3660,7 +3660,7 @@ ru:
permission_comment_news: "Комментировать новость"
permission_commit_access: "Возможность чтения/записи в репозиторий (commit)"
permission_copy_projects: "Копировать проект"
permission_copy_work_packages: "Duplicate work packages"
permission_copy_work_packages: "Дублировать пакеты работ"
permission_create_backup: "Создание резервных копий"
permission_delete_work_package_watchers: "Удалить наблюдателей"
permission_delete_work_packages: "Удалить пакеты работ"
+2 -2
View File
@@ -464,13 +464,13 @@ uk:
name: Відхилено
time_entry_activities:
item_0:
name: Налаштування
name: Керування
item_1:
name: Специфікація
item_2:
name: Розробка
item_3:
name: Перевірка
name: Тестування
item_4:
name: Підтримка
item_5:
+130 -130
View File
@@ -99,12 +99,12 @@ uk:
upgrade: "Оновити зараз"
contact: "Зв’яжіться з нами, щоб отримати демоверсію"
status:
expired: "Expired"
expiring_soon: "Expiring soon"
in_grace_period: "In grace period"
invalid_domain: "Invalid domain"
not_active: "Not active"
trial: "Trial"
expired: "Термін дії минув"
expiring_soon: "Термін дії незабаром мине"
in_grace_period: "У пільговому періоді"
invalid_domain: "Недійсний домен"
not_active: "Неактивний"
trial: "Пробний період"
jemalloc_allocator: Розподіл пам'яті Jemalloc
journal_aggregation:
explanation:
@@ -112,57 +112,57 @@ uk:
link: "вебгука"
scim_clients:
authentication_methods:
sso: "JWT from identity provider"
oauth2_client: "OAuth 2.0 client credentials"
oauth2_token: "Static access token"
sso: "JWT від постачальника ідентифікаційних даних"
oauth2_client: "Облікові дані клієнта OAuth 2.0"
oauth2_token: "Постійний токен доступу"
created_client_credentials_dialog_component:
title: "Client credentials created"
heading: "Client credentials have been generated"
one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now."
title: "Облікові дані клієнта створено"
heading: "Облікові дані клієнта згенеровано"
one_time_hint: "Це єдиний раз, коли ви побачите секретний ключ клієнта. Обов’язково скопіюйте його зараз."
created_token_dialog_component:
title: "Token created"
heading: "An token has been generated"
label_token: "Token"
one_time_hint: "This is the only time you will see this token. Make sure to copy it now."
title: "Токен створено"
heading: "Токен згенеровано"
label_token: "Токен"
one_time_hint: "Це єдиний раз, коли ви побачите цей токен. Обов’язково скопіюйте його зараз."
delete_scim_client_dialog_component:
title: "Delete SCIM client"
heading: "Are you sure you want to delete this SCIM client?"
description: "Users managed by this SCIM client can no longer be updated by it."
title: "Видалити клієнт SCIM"
heading: "Ви дійсно хочете видалити цей клієнт SCIM?"
description: "Цей клієнт SCIM більше не може оновлювати користувачів, якими керує."
edit:
label_delete_scim_client: "Delete SCIM client"
label_delete_scim_client: "Видалити клієнт SCIM"
form:
auth_provider_description: "This is the service that users added by the SCIM provider will use to authenticate in OpenProject."
authentication_method_description_html: "This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the <code>scim_v2</code> scope."
description_html: 'Please refer to our <a href="%{docs_url}">documentation on configuring SCIM clients</a> for more information on these configuration options.'
jwt_sub_description_html: 'For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult <a href="%{docs_url}">our documentation</a> to learn how to find the subject claim for your use case.'
name_description: "Choose a name that will help other admins better understand why this client was configured."
auth_provider_description: "Це сервіс, який користувачі, додані SCIM-провайдером, будуть використовувати для автентифікації в OpenProject."
authentication_method_description_html: "Ось так клієнт SCIM автентифікується в OpenProject. Переконайтеся, що токени OAuth включають область <code>scim_v2</code>."
description_html: 'Щоб отримати додаткову інформацію про налаштування клієнтів SCIM, перегляньте <a href="%{docs_url}">документацію</a>.'
jwt_sub_description_html: 'Наприклад, для Keycloak це UUID пов’язаного з клієнтом SCIM облікового запису сервісу. Інформацію про те, як знайти тему для свого варіанту використання, наведено в <a href="%{docs_url}">документації</a>.'
name_description: "Виберіть ім’я, яке допоможе іншим адміністраторам чітко зрозуміти, навіщо налаштовано цей клієнт."
index:
description: "SCIM clients configured here are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups."
label_create_button: "Add SCIM client"
description: "Клієнти SCIM, налаштовані тут, можуть взаємодіяти з API сервера SCIM в OpenProject, щоб надавати, оновлювати й видаляти облікові записи й групи."
label_create_button: "Додати клієнт SCIM"
new:
title: "New SCIM client"
title: "Новий клієнт SCIM"
revoke_static_token_dialog_component:
confirm_button: "Revoke"
title: "Revoke static token"
heading: "Are you sure you want to revoke this token?"
description: "The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API."
confirm_button: "Анулювати"
title: "Анулювати статичний токен"
heading: "Ви дійсно хочете анулювати цей токен?"
description: "Клієнт SCIM, який використовує цей токен, більше не зможе отримати доступ до API сервера SCIM в OpenProject."
table_component:
blank_slate:
title: "No SCIM clients configured yet"
description: "Add clients to see them here"
user_count: "Users"
title: "Немає налаштованих клієнтів SCIM"
description: "Додайте клієнтів, щоб побачити їх тут"
user_count: "Користувачі"
token_list_component:
description: "The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API."
heading: "Tokens"
label_add_token: "Token"
label_aria_add_token: "Add token"
description: "Клієнт SCIM може передавати токени, які ви генеруєте тут, для доступу до API сервера SCIM в OpenProject."
heading: "Токени"
label_add_token: "Токен"
label_aria_add_token: "Додати токен"
token_table_component:
blank_slate:
title: "No tokens have been created yet"
description: "You can create one now"
expired: "Expired on %{date}"
revoked: "Revoked on %{date}"
title: "Access token table"
title: "Ще не створено жодного токена"
description: "Його можна створити прямо зараз"
expired: "Термін дії минув %{date}"
revoked: "Анульовано %{date}"
title: "Таблиця токенів доступу"
authentication:
login_and_registration: "Вхід і реєстрація"
announcements:
@@ -325,7 +325,7 @@ uk:
admin_only: "Установіть прапорець, щоб зробити цей атрибут видимим лише для адміністраторів. Користувачі без прав адміністратора не зможуть переглядати або редагувати його."
is_filter: >
Дозвольте відображати користувацьке поле у фільтрі в поданнях пакета робіт. Зверніть увагу: користувацьке поле відображатиметься в глобальних поданнях, лише якщо встановлено прапорець «Для всіх проєктів».
formula: "Add numeric values or type / to search for an attribute or a mathematical operator."
formula: "Додайте числові значення або введіть \"/\" для пошуку атрибута чи математичного оператора."
tab:
no_results_title_text: Наразі немає спеціальних полів.
no_results_content_text: Створіть нове спеціальне поле
@@ -339,7 +339,7 @@ uk:
not_found: "не знайдено."
rules:
copy_workflow_from:
workflow_missing: "has no own workflow."
workflow_missing: "не має власного робочого процесу."
item:
root_item: "не може бути кореневим елементом."
not_persisted: "має бути вже наявним елементом."
@@ -350,8 +350,8 @@ uk:
parent:
not_descendant: "має бути нащадком кореня ієрархії."
rules:
copy_workflow_from: "Type for workflow copy"
enabled: "Enabled"
copy_workflow_from: "Тип копії робочого процесу"
enabled: "Увімкнено"
depth: "Глибина"
item: "Елемент"
label: "Мітка"
@@ -898,7 +898,7 @@ uk:
required_description: "Позначає цей робочий пакет як обовʼязковий для звʼязаного з ним"
label_parent_singular: "батьківський елемент"
label_parent_plural: "батьківський елемент"
label_other_relations: "Other relations"
label_other_relations: "Інші зв’язки"
ghost_relation_title: "Пов’язаний пакет робіт"
ghost_relation_description: "Ви не маєте дозволів на перегляд цього елемента."
label_invitation: Запрошення
@@ -953,7 +953,7 @@ uk:
attribute_name: "Атрибут"
help_text: "Довідковий текст"
auth_provider:
scim_clients: "SCIM clients"
scim_clients: "Клієнти SCIM"
capability:
context: "Контекст"
changeset:
@@ -967,7 +967,7 @@ uk:
default_value: "Значення за замовчуванням"
editable: "Може бути відредаговане"
field_format: "Формат"
formula: "Formula"
formula: "Формула"
is_filter: "Використовується як фільтр"
is_for_all: "Для всіх проєктів"
is_required: "Обов'язкове поле"
@@ -996,19 +996,19 @@ uk:
enterprise_token:
starts_at: "Дійсний з"
subscriber: "Абонент"
subscription: "Subscription"
subscription: "Підписка"
plan: "План"
encoded_token: "Маркер підтримки підприємства"
active_user_count_restriction: "Active users"
active_user_count_restriction: "Активні користувачі"
enterprise_trial:
company: "Company"
company: "Компанія"
grids/grid:
page: "Сторінка"
row_count: "Кількість рядків"
column_count: "Кількість стовпців"
widgets: "Віджети"
journal:
notes: "Нотатки"
notes: "Примітки"
cause_type: "Тип причини"
ldap_auth_source:
account: "Обліковий запис"
@@ -1119,9 +1119,9 @@ uk:
role:
permissions: "Дозволи"
scim_client:
auth_provider: "Authentication provider"
authentication_method: "Authentication method"
jwt_sub: "Subject claim"
auth_provider: "Постачальник послуг автентифікації"
authentication_method: "Метод автентифікації"
jwt_sub: "Потрібна тема"
status:
is_closed: "Робочий пакет закритий"
is_readonly: "Пакет робіт лише для перегляду"
@@ -1138,7 +1138,7 @@ uk:
ongoing: "Триває"
type:
description: "Текст за умовчанням для опису"
attribute_groups: "Form configuration"
attribute_groups: "Конфігурація форми"
is_in_roadmap: "За умовчанням відображається в дорожній карті"
is_default: "Активовано для нових проектів за замовчуванням"
is_milestone: "Це віха"
@@ -1165,24 +1165,24 @@ uk:
password_confirmation: "Підтвердження"
consented_at: "Згоден на"
group:
identity_url: "Identity URL"
identity_url: "URL-адреса ідентичності"
user_preference:
header_look_and_feel: "Look and feel"
header_alerts: "Alerts"
button_update_look_and_feel: "Update look and feel"
button_update_alerts: "Update alerts"
comments_sorting: "Display work package activity sorted by"
disable_keyboard_shortcuts: "Disable keyboard shortcuts"
header_look_and_feel: "Оформлення"
header_alerts: "Сповіщення"
button_update_look_and_feel: "Оновити оформлення"
button_update_alerts: "Сповіщення про оновлення"
comments_sorting: "Відображати дії з пакетів робіт, відсортовані за"
disable_keyboard_shortcuts: "Вимкнути комбінації клавіш"
disable_keyboard_shortcuts_caption_html: |-
You can choose to disable default <a href="%{href}">keyboard shortcuts</a> if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
Ви можете вимкнути стандартні <a href="%{href}">комбінації клавіш</a>, якщо використовуєте невізуальний екран або хочете, щоб відповідні дії не запускалися, коли ви натискаєте ці комбінації клавіш випадково.
dismissed_enterprise_banners: "Приховані банери Enterprise"
impaired: "Режим спеціальних можливостей"
auto_hide_popups: "Automatically hide success banners"
auto_hide_popups_caption: "When enabled, the green success banners will automatically disappear after 5 seconds."
warn_on_leaving_unsaved: "Попереджати мене, коли залишив робочий пакет із незбереженими змінами"
theme: "Colour mode"
theme: "Колірний режим"
time_zone: "Часовий пояс"
mode_guideline: "Some modes will overwrite custom theme colours for accessibility and legibility. Please select Light mode for full custom theme support."
mode_guideline: "Деякі режими перезаписують користувацькі кольори теми, щоб поліпшити доступність і читабельність. Виберіть світлий режим, якщо потрібна повна підтримка користувацької теми."
daily_reminders: "Щоденні нагадування"
workdays: "Робочі дні"
version:
@@ -1251,7 +1251,7 @@ uk:
error_unauthorized: "– можливо, немає доступу."
error_readonly: "– було здійснено спробу запису, але елемент недоступний для запису."
error_conflict: "Інформація була оновлена принаймні один інший користувач в той же час."
error_not_found: "not found."
error_not_found: "не знайдено."
email: "– це не дійсна електронна адреса."
empty: "не може бути порожнім."
enterprise_plan_required: "потрібен принаймні план %{plan_name}."
@@ -1269,7 +1269,7 @@ uk:
inclusion: "не встановлено в одному з допустимих значень"
inclusion_nested: "– не призначено одне з дозволених значень за шляхом «%{path}»."
invalid: "зазначено невірно"
invalid_characters: "contains invalid characters."
invalid_characters: "містить недопустимі символи."
invalid_url: "не є дійсною URL-адресою."
invalid_url_scheme: "не є підтримуваним протоколом (дозволено: %{allowed_schemes})."
less_than_or_equal_to: "має бути меншим або рівним %{count}"
@@ -1346,13 +1346,13 @@ uk:
scopes:
not_match_configured: "не відповідає доступним областям."
enterprise_trial:
already_used: "was already used to create a trial."
failed_to_create: "Trial could not be created (%{status})"
general_consent: "Please accept the terms and conditions."
already_used: "уже використано для створення пробної версії."
failed_to_create: "Не вдалося створити пробну версію (%{status})"
general_consent: "Будь ласка, прийміть правила й умови використання."
enterprise_token:
only_one_trial: "Only one trial token can be active. Please delete the previous trial token before adding another."
only_one_trial: "Активним може бути лише один пробний токен. Видаліть попередній, перш ніж додавати новий."
unreadable: "неможливо прочитати. Ви впевнені, що це маркер підтримки?"
already_added: "This token has already been added."
already_added: "Цей токен уже додано."
grids/grid:
overlaps: "перекриваються."
outside: "знаходиться за межами сітки."
@@ -1663,10 +1663,10 @@ uk:
many: "Роль"
other: "Роль"
scim_client:
one: "SCIM client"
few: "SCIM clients"
many: "SCIM clients"
other: "SCIM clients"
one: "Клієнт SCIM"
few: "Клієнти SCIM"
many: "Клієнти SCIM"
other: "Клієнти SCIM"
status: "Стан робочого пакета"
token/api:
one: Маркер доступу
@@ -1756,7 +1756,7 @@ uk:
blocks_ids: "Ідентифікатори заблокованих робочих пакетів"
category: "Категорія"
comment: "Коментар"
comments: "Коментування"
comments: "Коментар"
content: "Контент"
color: "Колір"
creator: "Автор"
@@ -1773,13 +1773,13 @@ uk:
derived_start_date: "Отримана дата початку"
direction: "Напрямок"
display_sums: "Відображати суми"
domain: "Domain"
domain: "Домен"
due_date: "Дата закінчення"
estimated_hours: "Робота"
estimated_time: "Робота"
email: "Ел. адреса"
entity_type: "Сутність"
expires_at: "Expires on"
expires_at: "Термін дії минає"
firstname: "Ім'я"
filter: "Фільтр"
group: "Група"
@@ -1793,14 +1793,14 @@ uk:
#kept for backwards compatibility
issue: "Робочий пакет"
journal: "Журнал"
journal_notes: "Коментування"
journal_notes: "Коментар"
lastname: "Прізвище"
login: "Ім'я користувача"
lock_version: "Заблокувати версію"
mail: "Електронна пошта"
name: "Назва"
note: "Примітка"
notes: "Нотатки"
notes: "Примітки"
number: "Число"
options: "Опції"
operator: "Оператор"
@@ -1828,7 +1828,7 @@ uk:
title: "Назва"
type: "Тип"
typeahead: "Автозаповнення"
uid: "Unique identifier"
uid: "Унікальний ідентифікатор"
updated_at: "Оновлене"
updated_on: "Оновлено"
uploader: "Завантажувач"
@@ -1903,7 +1903,7 @@ uk:
button_delete_watcher: "Видалити спостерігача %{name}"
button_download: "Завантажити"
button_duplicate: "Дублювати"
button_duplicate_and_follow: "Duplicate and follow"
button_duplicate_and_follow: "Дублювати й стежити"
button_edit: "Редагувати"
button_edit_associated_wikipage: "Редагувати пов'язану Wiki сторінку: %{page_title}"
button_expand_all: "Розгорнути все"
@@ -1942,7 +1942,7 @@ uk:
button_unwatch: "Перестати відстежувати"
button_update: "Оновити"
button_upgrade: "Оновити"
button_buy_now: "Buy now"
button_buy_now: "Придбати зараз"
button_upload: "Завантажити"
button_view: "Перегляд"
button_watch: "Спостерігати"
@@ -1987,13 +1987,13 @@ uk:
text:
failed: 'Не вдалося скопіювати проект "%{source_project_name}" до проекту "%{target_project_name}".'
succeeded: 'Скопіював проект "%{source_project_name}" до "%{target_project_name}".'
source_project_label: "Project copied"
source_project_label: "Проєкт скопійовано"
copy_options:
dependencies_label: 'Copy from project'
dependencies_label: 'Скопіювати з проєкту'
create_project:
template_label: "Use template"
template_label: "Використовувати шаблон"
copy_options:
dependencies_label: 'Copy from template'
dependencies_label: 'Копіювати з шаблону'
create_wiki_page: "Створити нову вікісторінку"
create_wiki_page_button: "Сторінка Wiki"
date:
@@ -2144,10 +2144,10 @@ uk:
other: "%{count} с"
units:
minute_abbreviated:
one: "min"
few: "mins"
many: "mins"
other: "mins"
one: "хв"
few: "хв"
many: "хв"
other: "хв"
hour:
one: "година"
few: "годин(и)"
@@ -2176,7 +2176,7 @@ uk:
description_category_reassign: "Оберіть категорію"
description_message_content: "Зміст повідомлення"
description_my_project: "Ви учасник"
description_notes: "Нотатки"
description_notes: "Примітки"
description_parent_work_package: "Батьківський робочий пакет поточного"
description_project_scope: "Область пошуку"
description_query_sort_criteria_attribute: "Критерій сортування"
@@ -2222,9 +2222,9 @@ uk:
plan_title: "Доповнення версії Enterprise %{plan}"
plan_name: "Версія Enterprise %{plan}"
plan_text_html: "Доступно, починаючи з версії %{plan_name}."
unlimited: "Unlimited"
unlimited: "Без обмежень"
already_have_token: >
Already have a token? Add it using the button below to upgrade to the booked Enterprise plan.
Уже маєте токен? Додайте його за допомогою кнопки нижче, щоб перейти на вже замовлений план Enterprise.
hide_banner: "Приховати цей банер"
homescreen_description: >
Плани Enterprise розширюють можливості вашого випуску OpenProject Community. Вони включають доповнення, додаткові функції та професійну підтримку, що дає організаціям усіх розмірів досягати кращих результатів разом.
@@ -2232,15 +2232,15 @@ uk:
baseline_comparison:
description: Виділяйте зміни, внесені в цей список із будь-якого моменту часу в минулому.
benefits:
description: "What are the benefits of the Enterprise on-premises edition?"
high_security: "Security features"
high_security_text: "Single sign on (SAML, OpenID Connect, CAS), LDAP groups."
installation: "Installation support"
installation_text: "Experienced software engineers guide you through the complete installation and setup process in your own infrastructure."
premium_features: "Enterprise add-ons"
premium_features_text: "Agile boards, custom theme and logo, graphs, intelligent workflows with custom actions, full text search for work package attachments and multi-select custom fields."
professional_support: "Professional support"
professional_support_text: "Get reliable, high-touch support from senior support engineers with expert knowledge about running OpenProject in business-critical environments."
description: "Які переваги локальної версії Enterprise?"
high_security: "Функції безпеки"
high_security_text: "Єдиний вхід (SAML, OpenID Connect, CAS), групи LDAP."
installation: "Підтримка під час установлення"
installation_text: "Досвідчені інженери з програмного забезпечення допоможуть вам виконати повне встановлення й налаштувати процес у вашій інфраструктурі."
premium_features: "Доповнення версії Enterprise"
premium_features_text: "Дошки Agile, власні тема й логотип, діаграми, розумні робочі процеси з користувацькими діями, повнотекстовий пошук у вкладеннях пакетів робіт і користувацькі поля з множинним вибором."
professional_support: "Професійна підтримка"
professional_support_text: "Отримайте надійну, комплексну підтримку від старших інженерів, які знають усе про запуск OpenProject у критично важливих для бізнесу середовищах."
work_package_subject_generation:
description: "Створюйте автоматично згенеровані теми на основі атрибутів і тексту."
customize_life_cycle:
@@ -2292,22 +2292,22 @@ uk:
description: " "
teaser:
title:
one: "One day left of %{trial_plan} trial token"
few: "%{count} days left of %{trial_plan} trial token"
many: "%{count} days left of %{trial_plan} trial token"
other: "%{count} days left of %{trial_plan} trial token"
description: "You have access to all %{trial_plan} features."
one: "Один день до завершення строку дії пробного токена %{trial_plan}"
few: "%{count} дні до завершення строку дії пробного токена %{trial_plan}"
many: "%{count} днів до завершення строку дії пробного токена %{trial_plan}"
other: "%{count} дня до завершення строку дії пробного токена %{trial_plan}"
description: "У вас є доступ до всіх функцій %{trial_plan}."
trial:
not_found: "You have requested a trial token, but that request is no longer available. Please try again."
wait_for_confirmation: "We sent you an email to confirm your address in order to retrieve a trial token."
not_found: "Ви запитали пробний токен, але цей запит більше не доступний. Спробуйте ще раз."
wait_for_confirmation: "Ми надіслали вам електронного листа, щоб ви підтвердили свою адресу й могли отримати пробний токен."
already_retrieved: >
Your trial enterprise token was already retrieved. Please check your emails for the token being attached. Please reach out to our support team if you need a new one.
successfully_saved: "Your trial enterprise token has been successfully retrieved."
token_sent: "Trial token requested"
request_again: "Request again"
resend_action: "Resend confirmation email"
welcome_title: "Quick feature overview"
welcome_description: "Get a quick overview of project management and team collaboration with OpenProject Enterprise edition."
Токен пробної версії Enterprise уже отримано. На вашу електронну пошту мав надійти лист із вкладеним токеном. Якщо вам потрібен новий токен, зверніться до нашої служби підтримки.
successfully_saved: "Токен пробної версії Enterprise успішно отримано."
token_sent: "Запитано пробний токен"
request_again: "Надіслати запит ще раз"
resend_action: "Ще раз надіслати електронного листа для підтвердження"
welcome_title: "Стислий огляд функцій"
welcome_description: "Отримайте стислий огляд функцій для управління проєктами й командної роботи у версії OpenProject Enterprise."
confirmation_info: >
We sent you an email on %{date} to %{email} with all the information to start the free trial of OpenProject Enterprise. Please check your inbox and click the confirmation link provided to start your 14-day free trial.
confirmation_subline: >
@@ -2346,7 +2346,7 @@ uk:
error_enterprise_token_invalid_domain: "Версія Enterprise не активна. Домен вашого маркера Enterprise (%{actual}) не збігається з іменем хосту системи (%{expected})."
error_failed_to_delete_entry: "Не вдалося видалити цей запис."
error_in_dependent: "Помилка спроби змінити залежний об'єкт: %{dependent_class} %{related_id} - %{related_subject}: %{error}"
error_in_new_dependent: "Не вдалося змінити залежний об’єкт: %{dependent_class} %{related_subject}: %{error}"
error_in_new_dependent: "Не вдалося створити залежний об’єкт: %{dependent_class} %{related_subject}: %{error}"
error_invalid_selected_value: "Недійсне вибране значення."
error_journal_attribute_not_present: "Журнал не містить атрибута %{attribute}."
error_pdf_export_too_many_columns: "Для експорту PDF-файлу вибрано занадто багато стовпців. Скоротіть кількість стовпців."
@@ -2690,7 +2690,7 @@ uk:
label_active: "Активні"
label_activate_user: "Активні користувачі"
label_active_in_new_projects: "Активна участь в нових проектах"
label_activity: "Дія"
label_activity: "Активність"
label_add_edit_translations: "Додайте та редагуйте переклади"
label_add_another_file: "Додати ще один файл"
label_add_columns: "Додати вибрані стовпці"
@@ -3310,7 +3310,7 @@ uk:
label_work_package_attachments: "Додатки до робочого пакету"
label_work_package_category_new: "Нова категорія"
label_work_package_category_plural: "Категорії робочих пакетів"
label_work_package_comments: "Work package comments"
label_work_package_comments: "Коментарі до пакета робіт"
label_work_package_hierarchy: "Ієрархія робочих пакетів"
label_work_package_new: "Новий пакет робіт"
label_work_package_edit: "Редагувати робочий пакет %{name}"
@@ -3633,7 +3633,7 @@ uk:
text_getting_started_description: "Отримайте стислий огляд управління проєктами та командної роботи в OpenProject. Це відео можна переглянути знову в меню довідки."
welcome: "Вітаємо в додатку %{app_title}"
select_language: "Будь ласка, виберіть мову"
permission_add_work_package_comments: "Додати коментарі"
permission_add_work_package_comments: "Додавання коментарів"
permission_add_work_packages: "Додати робочі пакети"
permission_add_messages: "Відправка повідомлень"
permission_add_project: "Створення проєктів"
@@ -3664,12 +3664,12 @@ uk:
permission_delete_timelines: "Видалити графіки"
permission_delete_wiki_pages: "Видалення wiki-сторінок"
permission_delete_wiki_pages_attachments: "Видалення прикріплених файлів"
permission_edit_work_package_comments: "Модерувати коментарі"
permission_edit_work_package_comments: "Модерування коментарів"
permission_edit_work_package_comments_explanation: "Увага! Користувачі із цим дозволом можуть редагувати будь-чиї коментарі."
permission_edit_work_packages: "Редагувати робочі пакети"
permission_edit_messages: "Редагування повідомлень"
permission_edit_own_internal_comments: "Редагування власних внутрішніх коментарів"
permission_edit_own_work_package_comments: "Редагувати власні коментарі"
permission_edit_own_work_package_comments: "Редагування власних коментарів"
permission_edit_own_messages: "Редагування власних повідомлень"
permission_edit_own_time_entries: "Редагування власного обліку часу"
permission_edit_others_internal_comments: "Модерування внутрішніх коментарів"
@@ -3731,7 +3731,7 @@ uk:
permission_view_wiki_pages: "Перегляд wiki"
permission_work_package_assigned: "Стати виконавцем / відповідальним"
permission_work_package_assigned_explanation: "Робочі пакети можуть бути призначені користувачам і групам, які володіють цією роллю у відповідному проекті"
permission_view_project_activity: "Перегляд активності в проєкті"
permission_view_project_activity: "Перегляд дій у проєкті"
permission_view_project_attributes: "Переглянути атрибути проєкту"
permission_view_project_phases: "Перегляд етапів проєкту"
permission_save_bcf_queries: "Зберігання запитів BCF"
@@ -3773,7 +3773,7 @@ uk:
use_template: "Використовувати шаблон"
make_template: "Установити як шаблон"
remove_from_templates: "Вилучити із шаблонів"
project_module_activity: "Дія"
project_module_activity: "Активність"
project_module_forums: "Форуми"
project_module_work_package_tracking: "Пакети робіт"
project_module_news: "Новини"
+8 -12
View File
@@ -137,29 +137,25 @@ Rails.application.routes.draw do
get "/roles/workflow/:id/:role_id/:type_id" => "roles#workflow"
resources :types do
resource :form_configuration, only: %i[edit update], controller: "work_package_types/form_configuration_tab"
resource :projects, controller: "work_package_types/projects_tab", only: %i[update edit] do
resources :types, module: "work_package_types", except: [:update] do
resource :form_configuration, only: %i[edit update], controller: "form_configuration_tab"
resource :projects, controller: "projects_tab", only: %i[update edit] do
collection do
post :enable_all, to: "work_package_types/projects_tab#enable_all_projects"
post :enable_all, to: "projects_tab#enable_all_projects"
end
end
resource :settings, controller: "work_package_types/settings_tab", only: %i[update edit]
resource :subject_configuration, controller: "work_package_types/subject_configuration_tab", only: %i[update edit]
member do
get "edit/:tab" => "types#edit", as: "edit_tab"
match "update/:tab" => "types#update", as: "update_tab", via: %i[post patch]
end
resource :settings, controller: "settings_tab", only: %i[update edit]
resource :subject_configuration, controller: "subject_configuration_tab", only: %i[update edit]
resources :pdf_export_template, only: %i[],
controller: "work_package_types/pdf_export_template",
controller: "pdf_export_template",
path: "pdf_export_template" do
member do
post :toggle
put :drop
end
collection do
get :edit
put :enable_all
put :disable_all
end
-1
View File
@@ -20,7 +20,6 @@ get:
+ id: Sort by the version id
+ name: Sort by the version name using numeric collation, comparing sequences of decimal digits by their numeric value
+ semver_name: Deprecated, use name instead
example: '[["name", "desc"]]'
in: query
name: sortBy
@@ -19,6 +19,21 @@ All controllers live under `frontend/src/stimulus/controllers/`. The naming conv
If you want to add a common pattern, manually register the controller under `frontend/src/stimulus/setup.ts`. Often you'll want to have a dynamically loaded controller instead though.
### Adding a static controller from a plugin
If you want to add a stimulus controller from plugin code, you can do so by manually adding it to the preregister:
```typescript
import { OpenProjectStimulusApplication } from 'core-app/stimulus/app';
import { MyTestControllerClass } from './test/foo/my-test.controller';
OpenProjectStimulusApplication.preregister(
'test',
MyTestControllerClass
);
```
### Dynamically loaded controllers
To dynamically load a controller, it needs to live under `frontend/src/stimulus/controllers/dynamic/<controller-name>.controller.ts`.
@@ -41,6 +56,21 @@ If you want to organize your dynamic controllers in a subfolder, use the [double
You need to take care to prefix all actions, values etc. with the exact same pattern, e.g., `data-admin--settings-target="foobar"`.
#### Dynamically loading controllers from plugins
If you want to add a dynamic stimulus controller import from plugin code, you can do so by manually adding it to the preregister:
```typescript
import { OpenProjectStimulusApplication } from 'core-app/stimulus/app';
OpenProjectStimulusApplication.preregisterDynamic(
'test',
() => import('./test.controller')
);
```
This ensures that the controller is loaded only when it is needed, and not at application startup. The controller will then be engaded when the `data-controller` attribute is present in the DOM through the same mechanism as for the core dynamic controllers.
### Requiring a page controller
If you have a single controller used in a partial, we have added a helper to use in a partial in order to append a controller to the `#content-wrapper` tag. This is useful if your template doesn't have a single DOM root. For example, to load the dynamic `project-storage-form` controller and provide a custom value to it:
@@ -49,7 +49,7 @@ You can configure the following options for webhooks:
3. Freely choose an additional **description** to further identify the intent of the respective webhook.
4. By defining a **Signature secret** you guarantee that the sender of the payload request is actually OpenProject. The client will then check this signature secret.
5. **Enable** if **the webhook** should be active.
6. **Set the events** for which the webhook should be activate, i.e. webhook for updating or creating projects or work packages, or for creating time entries.
6. **Set the events** for which the webhook should be activate, i.e. webhook for updating or creating projects, work packages, work package comments, time entries and attachments.
7. **Select for which projects the webhook should be active**. You can choose all projects or only specific projects. For example if you select the project "System admin guide", an event (ie. create a new time entry) will be fired via the webhook. This will only happen if a user logs time within the selected projects.
8. Press the green **Create** button to save your changes (you may nee to scroll down to find it). There you can also cancel your input.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 92 KiB

@@ -1,3 +1,6 @@
<op-breadcrumbs
[items]="breadcrumbItems()"
></op-breadcrumbs>
<div class="toolbar-container">
<div class="toolbar">
<div class="title-container">
@@ -41,6 +41,8 @@ import { GlobalSearchService } from 'core-app/core/global_search/services/global
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { OpTitleService } from 'core-app/core/html/op-title.service';
@Component({
selector: 'opce-global-search-title',
@@ -71,6 +73,8 @@ export class GlobalSearchTitleComponent extends UntilDestroyedMixin implements O
readonly globalSearchService:GlobalSearchService,
readonly I18n:I18nService,
readonly injector:Injector,
protected readonly pathHelper:PathHelperService,
protected readonly titleService:OpTitleService,
) {
super();
}
@@ -95,6 +99,23 @@ export class GlobalSearchTitleComponent extends UntilDestroyedMixin implements O
});
}
breadcrumbItems() {
const items = [];
items.push({
href: this.pathHelper.homePath(),
text: this.titleService.appTitle,
});
if (this.currentProjectService?.identifier) {
items.push({
href: this.pathHelper.projectPath(this.currentProjectService.identifier),
text: this.currentProjectService.name,
});
}
items.push(this.searchTitle);
return items;
}
private projectText(scope:string):string {
const currentProjectName = this.currentProjectService.name ? this.currentProjectService.name : '';
@@ -37,4 +37,29 @@ import { WorkPackageCopyController } from 'core-app/features/work-packages/compo
})
export class WorkPackageCopyFullViewComponent extends WorkPackageCopyController {
public successState = 'work-packages.show';
breadcrumbItems() {
const items = [];
items.push({
href: this.pathHelper.homePath(),
text: this.titleService.appTitle,
});
if (this.currentProjectService?.identifier) {
items.push({
href: this.pathHelper.projectPath(this.currentProjectService.identifier),
text: this.currentProjectService.name,
});
}
items.push({
href: this.pathHelper.workPackagesPath(this.currentProjectService.identifier as string),
text: this.I18n.t('js.label_work_package_plural'),
});
items.push({
href: this.pathHelper.projectWorkPackagePath(this.currentProjectService.identifier as string, this.stateParams.copiedFromWorkPackageId as string),
text: this.newWorkPackage.subject,
});
items.push(I18n.t('js.button_duplicate'));
return items;
}
}
@@ -1,4 +1,3 @@
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { TableEventComponent } from 'core-app/features/work-packages/components/wp-fast-table/handlers/table-handler-registry';
import { WorkPackageTable } from '../wp-fast-table';
@@ -6,7 +5,7 @@ import { WorkPackageTable } from '../wp-fast-table';
* Execute the callback if the given JQuery Event is either an ENTER key or a click
*/
export function onClickOrEnter(evt:JQuery.TriggeredEvent, callback:() => void) {
if (evt.type === 'click' || (evt.type === 'keydown' && evt.which === KeyCodes.ENTER)) {
if (evt.type === 'click' || (evt.type === 'keydown' && evt.key === 'Enter')) {
callback();
}
}
@@ -1,5 +1,4 @@
import { Injector } from '@angular/core';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { TableEventComponent } from 'core-app/features/work-packages/components/wp-fast-table/handlers/table-handler-registry';
import { ContextMenuHandler } from './context-menu-handler';
@@ -23,7 +22,7 @@ export class ContextMenuKeyboardHandler extends ContextMenuHandler {
const target = jQuery(evt.target);
if (!(evt.keyCode === KeyCodes.F10 && evt.shiftKey && evt.altKey)) {
if (!(evt.key === 'F10' && evt.shiftKey && evt.altKey)) {
return true;
}
@@ -57,6 +57,7 @@ import { OpTitleService } from 'core-app/core/html/op-title.service';
import { WorkPackageCreateService } from './wp-create.service';
import { HalError } from 'core-app/features/hal/services/hal-error';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
@Directive()
export class WorkPackageCreateComponent extends UntilDestroyedMixin implements OnInit {
@@ -97,7 +98,9 @@ export class WorkPackageCreateComponent extends UntilDestroyedMixin implements O
protected readonly wpTableFilters:WorkPackageViewFiltersService,
protected readonly pathHelper:PathHelperService,
protected readonly apiV3Service:ApiV3Service,
protected readonly cdRef:ChangeDetectorRef) {
protected readonly currentProjectService:CurrentProjectService,
protected readonly cdRef:ChangeDetectorRef,
) {
super();
}
@@ -37,4 +37,25 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
})
export class WorkPackageNewFullViewComponent extends WorkPackageCreateComponent {
public successState = this.$state.current.data.successState as string;
breadcrumbItems() {
const items = [];
items.push({
href: this.pathHelper.homePath(),
text: this.titleService.appTitle,
});
if (this.currentProjectService?.identifier) {
items.push({
href: this.pathHelper.projectPath(this.currentProjectService.identifier),
text: this.currentProjectService.name,
});
}
items.push({
href: this.pathHelper.workPackagesPath(this.currentProjectService.identifier as string),
text: this.I18n.t('js.label_work_package_plural'),
});
items.push(I18n.t('js.label_create_work_package'));
return items;
}
}
@@ -1,6 +1,9 @@
<div
class="work-package--new-state work-packages--new work-packages--show-view toolbar-container"
*ngIf="newWorkPackage">
<op-breadcrumbs
[items]="breadcrumbItems()"
></op-breadcrumbs>
<edit-form [resource]="newWorkPackage"
[skippedFields]="['status', 'type']"
[inEditMode]="true"
@@ -122,7 +122,7 @@ export class WorkPackageRelationRowComponent extends UntilDestroyedMixin impleme
}
public handleDescriptionKey($event:JQuery.TriggeredEvent) {
if ($event.which === 27) {
if ($event.key === 'Escape') {
this.cancelDescriptionEdit();
}
}
@@ -29,7 +29,6 @@
import { Injector } from '@angular/core';
import * as moment from 'moment';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { LoadingIndicatorService } from 'core-app/core/loading-indicator/loading-indicator.service';
import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service';
@@ -128,7 +127,7 @@ export function registerWorkPackageMouseHandler(this:void,
function keyPressFn(ev:JQuery.TriggeredEvent) {
const kev:KeyboardEvent = ev.originalEvent as KeyboardEvent;
if (kev.keyCode === KeyCodes.ESCAPE) {
if (kev.key === 'Escape') {
deactivate(null, true);
}
}
@@ -45,7 +45,7 @@ export type BreadcrumbItem =
})
export class OpBreadcrumbsComponent {
@Input() items:BreadcrumbItem[] = [];
@Input() lastItemSection:string | null = null;
@Input() lastItemSection?:string | null = null;
constructor(
readonly I18n:I18nService,
@@ -36,7 +36,7 @@ import {
ICKEditorWatchdog,
} from 'core-app/shared/components/editor/components/ckeditor/ckeditor.types';
import { CKEditorSetupService } from 'core-app/shared/components/editor/components/ckeditor/ckeditor-setup.service';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { KeyCodes } from 'core-app/shared/helpers/keycodes';
import { debugLog } from 'core-app/shared/helpers/debug_output';
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
@@ -271,13 +271,13 @@ export class OpCkeditorComponent extends UntilDestroyedMixin implements OnInit,
editor.editing.view.document,
'keydown',
(evt, data) => {
if ((data.ctrlKey || data.metaKey) && data.keyCode === Number(KeyCodes.ENTER)) {
if ((data.ctrlKey || data.metaKey) && data.keyCode === KeyCodes.ENTER) {
debugLog('Sending save request from CKEditor.');
this.saveRequested.emit();
evt.stop();
}
if (data.keyCode === Number(KeyCodes.ESCAPE)) {
if (data.keyCode === KeyCodes.ESCAPE) {
this.editorEscape.emit();
evt.stop();
}
@@ -26,7 +26,6 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { Injector } from '@angular/core';
@@ -139,7 +138,7 @@ export class HalResourceEditFieldHandler extends EditFieldHandler {
public async handleUserKeydown(event:JQuery.TriggeredEvent, onlyCancel = false) {
// Only handle submission in edit mode
if (this.inEditMode && !onlyCancel) {
if (event.which === KeyCodes.ENTER) {
if (event.key === 'Enter') {
await this.form.submit();
return false;
}
@@ -147,7 +146,7 @@ export class HalResourceEditFieldHandler extends EditFieldHandler {
}
// Escape editing when not in edit mode
if (event.which === KeyCodes.ESCAPE) {
if (event.key === 'Escape') {
this.handleUserCancel();
return false;
}
@@ -7,7 +7,6 @@ import {
OpContextMenuLocalsToken,
} from 'core-app/shared/components/op-context-menu/op-context-menu.types';
import { OPContextMenuComponent } from 'core-app/shared/components/op-context-menu/op-context-menu.component';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { FocusHelperService } from 'core-app/shared/directives/focus/focus-helper';
@Injectable({ providedIn: 'root' })
@@ -52,7 +51,7 @@ export class OPContextMenuService {
// Listen to keyups on window to close context menus
jQuery(window).on('keydown', (evt:JQuery.TriggeredEvent) => {
if (this.active && evt.which === KeyCodes.ESCAPE) {
if (this.active && evt.key === 'Escape') {
this.close(true);
}
@@ -11,7 +11,6 @@ import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { finalize, take } from 'rxjs/operators';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { HttpClient } from '@angular/common/http';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { ID } from '@datorama/akita';
import { IProjectData } from './project-data';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
@@ -85,16 +84,16 @@ export class SearchableProjectListService {
this.selectedItemID$
.pipe(take(1))
.subscribe((activeID) => {
switch (event.keyCode) {
case KeyCodes.UP_ARROW:
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
this.selectPreviousResult(activeID, projects);
break;
case KeyCodes.DOWN_ARROW:
case 'ArrowDown':
event.preventDefault();
this.selectNextResult(activeID, projects);
break;
case KeyCodes.ENTER:
case 'Enter':
event.stopPropagation();
event.preventDefault();
this.activateSelectedResult(event);
@@ -1,101 +0,0 @@
export enum KeyCodes {
BACKSPACE = 8,
TAB = 9,
ENTER = 13,
SHIFT = 16,
CTRL = 17,
ALT = 18,
PAUSE = 19,
CAPS_LOCK = 20,
ESCAPE = 27,
SPACE = 32,
PAGE_UP = 33,
PAGE_DOWN = 34,
END = 35,
HOME = 36,
LEFT_ARROW = 37,
UP_ARROW = 38,
RIGHT_ARROW = 39,
DOWN_ARROW = 40,
INSERT = 45,
DELETE = 46,
KEY_0 = 48,
KEY_1 = 49,
KEY_2 = 50,
KEY_3 = 51,
KEY_4 = 52,
KEY_5 = 53,
KEY_6 = 54,
KEY_7 = 55,
KEY_8 = 56,
KEY_9 = 57,
KEY_A = 65,
KEY_B = 66,
KEY_C = 67,
KEY_D = 68,
KEY_E = 69,
KEY_F = 70,
KEY_G = 71,
KEY_H = 72,
KEY_I = 73,
KEY_J = 74,
KEY_K = 75,
KEY_L = 76,
KEY_M = 77,
KEY_N = 78,
KEY_O = 79,
KEY_P = 80,
KEY_Q = 81,
KEY_R = 82,
KEY_S = 83,
KEY_T = 84,
KEY_U = 85,
KEY_V = 86,
KEY_W = 87,
KEY_X = 88,
KEY_Y = 89,
KEY_Z = 90,
LEFT_META = 91,
RIGHT_META = 92,
SELECT = 93,
NUMPAD_0 = 96,
NUMPAD_1 = 97,
NUMPAD_2 = 98,
NUMPAD_3 = 99,
NUMPAD_4 = 100,
NUMPAD_5 = 101,
NUMPAD_6 = 102,
NUMPAD_7 = 103,
NUMPAD_8 = 104,
NUMPAD_9 = 105,
MULTIPLY = 106,
ADD = 107,
SUBTRACT = 109,
DECIMAL = 110,
DIVIDE = 111,
F1 = 112,
F2 = 113,
F3 = 114,
F4 = 115,
F5 = 116,
F6 = 117,
F7 = 118,
F8 = 119,
F9 = 120,
F10 = 121,
F11 = 122,
F12 = 123,
NUM_LOCK = 144,
SCROLL_LOCK = 145,
SEMICOLON = 186,
EQUALS = 187,
COMMA = 188,
DASH = 189,
PERIOD = 190,
FORWARD_SLASH = 191,
GRAVE_ACCENT = 192,
OPEN_BRACKET = 219,
BACK_SLASH = 220,
CLOSE_BRACKET = 221,
SINGLE_QUOTE = 222,
}
@@ -0,0 +1,4 @@
export const KeyCodes = {
ENTER: 13,
ESCAPE: 27,
};
@@ -11,7 +11,6 @@ import {
TemplateRef,
ViewChild,
} from '@angular/core';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { findAllFocusableElementsWithin } from 'core-app/shared/helpers/focus-helpers';
import { SpotDropModalTeleportationService } from './drop-modal-teleportation.service';
@@ -218,7 +217,7 @@ export class SpotDropModalComponent implements OnDestroy {
}
private escapeCallback = (evt:KeyboardEvent) => {
if (evt.keyCode === KeyCodes.ESCAPE) {
if (evt.key === 'Escape') {
this.close();
}
};
@@ -27,7 +27,6 @@
min-width: 25vw
pre
background: #f1f1f1
padding: 5px
@@ -53,10 +53,9 @@ RB.EditableInplace = (function ($) {
j = $(this).parents('.model').first();
that = j.data('this');
// 13 is the key code of Enter, 27 of ESC.
if (e.which === 13) {
if (e.key === 'Enter') {
that.saveEdits();
} else if (e.which === 27) {
} else if (e.key === 'Escape') {
that.cancelEdit();
} else {
return true;
@@ -121,7 +121,7 @@ RB.Model = (function ($) {
},
],
close: function (e, ui) {
if (e.which === 1 || e.which === 27) {
if (e.type === 'click' || (e.type === 'keydown' && e.key === 'Escape')) {
self.cancelEdit();
}
},
@@ -49,7 +49,7 @@ export default class MenusSubtreeController extends Controller {
public toggle(event:MouseEvent|KeyboardEvent):void {
// ignore the event if a key different from ENTER was pressed.
if (event.type === 'keydown' && event.which !== 13) {
if (event.type === 'keydown' && (event as KeyboardEvent).key !== 'Enter') {
return;
}
@@ -10,6 +10,7 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service
import moment from 'moment';
import allLocales from '@fullcalendar/core/locales-all';
import { renderStreamMessage } from '@hotwired/turbo';
import { opStopwatchStopIconData, toDOMString } from '@openproject/octicons-angular';
export default class MyTimeTrackingController extends Controller {
private turboRequests:TurboRequestsService;
@@ -94,19 +95,28 @@ export default class MyTimeTrackingController extends Controller {
eventMinHeight: 30,
eventMaxStack: 2,
nowIndicator: true,
slotDuration: '00:15:00',
slotLabelInterval: '01:00',
businessHours: { daysOfWeek: this.workingDaysValue, startTime: '00:00', endTime: '24:00' },
hiddenDays: this.hiddenDays(),
firstDay: this.startOfWeekValue,
eventClassNames(info) {
return [
const classes = [
'calendar-time-entry-event',
`__hl_type_${info.event.extendedProps.typeId}`,
'__hl_border_top',
'ellipsis',
];
if (info.event.extendedProps.ongoing) {
classes.push('calendar-time-entry-event-ongoing');
}
return classes;
},
eventContent: (info) => {
let timeDetails = '';
let stopTimerButton = '';
let duration = info.event.extendedProps.hours as number;
if (info.isResizing && info.event.start && info.event.end) {
@@ -118,10 +128,14 @@ export default class MyTimeTrackingController extends Controller {
timeDetails = `<div class="fc-event-times" title="${time}">${time}</div>`;
}
if (info.event.extendedProps.ongoing) {
stopTimerButton = toDOMString(opStopwatchStopIconData, 'small', { 'aria-hidden': 'true', class: 'octicon stop-timer-button' });
}
return {
html: `
<div class="fc-event-main-frame">
<div class="fc-event-time">${this.displayDuration(duration)}</div>
<div class="fc-event-time">${stopTimerButton} ${this.displayDuration(duration)}</div>
<div class="fc-event-title-container">
<div class="fc-event-title fc-event-wp" title="${info.event.extendedProps.workPackageSubject}">
<a class="Link--primary Link" href="${this.pathHelper.workPackageShortPath(info.event.extendedProps.workPackageId as string)}">
@@ -188,6 +202,10 @@ export default class MyTimeTrackingController extends Controller {
return false;
}
if (draggedEvent?.extendedProps.ongoing) {
return false;
}
return true;
},
@@ -210,6 +228,9 @@ export default class MyTimeTrackingController extends Controller {
);
}
// mark the event explicitly as resizable if it is not an all day event
info.event.setProp('durationEditable', !info.event.allDay);
this.calendar.setOption('defaultTimedEventDuration', this.DEFAULT_TIMED_EVENT_DURATION);
},
eventClick: (info) => {
@@ -171,6 +171,12 @@ export default class TimeEntryController extends Controller {
// Parse input through our chronic duration parser and then reformat as hours that can be nicely parsed on the
// backend
const hours = this.parsedHourInput();
if (hours === 0) {
this.hoursInputTarget.value = '';
return;
}
this.hoursInputTarget.value = outputChronicDuration(hours, { format: 'hours_only' }) || '';
this.datesChanged(this.hoursInputTarget);
@@ -86,22 +86,14 @@ export default class IndexController extends Controller {
declare showConflictFlashMessageUrlValue:string;
declare unsavedChangesConfirmationMessageValue:string;
private handleWorkPackageUpdateBound:EventListener;
private handleVisibilityChangeBound:EventListener;
private rescueEditorContentBound:EventListener;
private handleTurboSubmitStartBound:EventListener;
private handleTurboSubmitEndBound:EventListener;
private onEscapeEditorBound:EventListener;
private adjustMarginBound:EventListener;
private onBlurEditorBound:EventListener;
private onFocusEditorBound:EventListener;
private updateInProgress:boolean;
private turboRequests:TurboRequestsService;
private apiV3Service:ApiV3Service;
private abortController = new AbortController();
private ckEditorAbortController = new AbortController();
async connect() {
const context = await window.OpenProject.getPluginContext();
this.turboRequests = context.services.turboRequests;
@@ -123,6 +115,7 @@ export default class IndexController extends Controller {
disconnect() {
this.rescueEditorContent();
this.removeEventListeners();
this.removeCkEditorEventListeners();
this.stopPolling();
this.markAsDisconnected();
}
@@ -145,30 +138,28 @@ export default class IndexController extends Controller {
}
private setupEventListeners() {
this.handleWorkPackageUpdateBound = () => { void this.handleWorkPackageUpdate(); };
this.handleVisibilityChangeBound = () => { void this.handleVisibilityChange(); };
this.rescueEditorContentBound = () => { void this.rescueEditorContent(); };
const { signal } = this.abortController;
this.handleTurboSubmitStartBound = (event:Event) => { void this.handleTurboSubmitStart(event); };
this.handleTurboSubmitEndBound = (event:Event) => { void this.handleTurboSubmitEnd(event); };
const handlers = {
workPackageUpdated: () => { void this.handleWorkPackageUpdate(); },
workPackageNotificationsUpdated: () => { void this.handleWorkPackageUpdate(); },
visibilityChange: () => { void this.handleVisibilityChange(); },
beforeUnload: () => { void this.rescueEditorContent(); },
turboSubmitStart: (event:Event) => { void this.handleTurboSubmitStart(event); },
turboSubmitEnd: (event:Event) => { void this.handleTurboSubmitEnd(event); },
};
document.addEventListener('work-package-updated', this.handleWorkPackageUpdateBound);
document.addEventListener('work-package-notifications-updated', this.handleWorkPackageUpdateBound);
document.addEventListener('visibilitychange', this.handleVisibilityChangeBound);
document.addEventListener('beforeunload', this.rescueEditorContentBound);
document.addEventListener('work-package-updated', handlers.workPackageUpdated, { signal });
document.addEventListener('work-package-notifications-updated', handlers.workPackageNotificationsUpdated, { signal });
document.addEventListener('visibilitychange', handlers.visibilityChange, { signal });
document.addEventListener('beforeunload', handlers.beforeUnload, { signal });
this.element.addEventListener('turbo:submit-start', this.handleTurboSubmitStartBound);
this.element.addEventListener('turbo:submit-end', this.handleTurboSubmitEndBound);
this.element.addEventListener('turbo:submit-start', handlers.turboSubmitStart, { signal });
this.element.addEventListener('turbo:submit-end', handlers.turboSubmitEnd, { signal });
}
private removeEventListeners() {
document.removeEventListener('work-package-updated', this.handleWorkPackageUpdateBound);
document.removeEventListener('work-package-notifications-updated', this.handleWorkPackageUpdateBound);
document.removeEventListener('visibilitychange', this.handleVisibilityChangeBound);
document.removeEventListener('beforeunload', this.rescueEditorContentBound);
this.element.removeEventListener('turbo:submit-start', this.handleTurboSubmitStartBound);
this.element.removeEventListener('turbo:submit-end', this.handleTurboSubmitEndBound);
this.abortController.abort();
}
private handleVisibilityChange() {
@@ -555,34 +546,32 @@ export default class IndexController extends Controller {
}
}
private addEventListenersToCkEditorInstance() {
this.onEscapeEditorBound = () => { void this.closeEditor(); };
this.adjustMarginBound = () => { void this.adjustJournalContainerMargin(); };
this.onBlurEditorBound = () => { void this.onBlurEditor(); };
this.onFocusEditorBound = () => {
void this.onFocusEditor();
if (this.isMobile()) {
void this.scrollInputContainerIntoView(200);
}
private addCkEditorEventListeners() {
const { signal } = this.ckEditorAbortController;
const handlers = {
onEscapeEditor: () => { void this.closeEditor(); },
adjustMargin: () => { void this.adjustJournalContainerMargin(); },
onBlurEditor: () => { void this.onBlurEditor(); },
onFocusEditor: () => {
void this.onFocusEditor();
if (this.isMobile()) { void this.scrollInputContainerIntoView(200); }
},
};
const editorElement = this.getCkEditorElement();
if (editorElement) {
editorElement.addEventListener('editorEscape', this.onEscapeEditorBound);
editorElement.addEventListener('editorKeyup', this.adjustMarginBound);
editorElement.addEventListener('editorBlur', this.onBlurEditorBound);
editorElement.addEventListener('editorFocus', this.onFocusEditorBound);
editorElement.addEventListener('editorEscape', handlers.onEscapeEditor, { signal });
editorElement.addEventListener('editorKeyup', handlers.adjustMargin, { signal });
editorElement.addEventListener('editorBlur', handlers.onBlurEditor, { signal });
editorElement.addEventListener('editorFocus', handlers.onFocusEditor, { signal });
}
}
private removeEventListenersFromCkEditorInstance() {
const editorElement = this.getCkEditorElement();
if (editorElement) {
editorElement.removeEventListener('editorEscape', this.onEscapeEditorBound);
editorElement.removeEventListener('editorKeyup', this.adjustMarginBound);
editorElement.removeEventListener('editorBlur', this.onBlurEditorBound);
editorElement.removeEventListener('editorFocus', this.onFocusEditorBound);
}
private removeCkEditorEventListeners() {
this.ckEditorAbortController.abort();
// Create a new AbortController for future CKEditor events
this.ckEditorAbortController = new AbortController();
}
private adjustJournalContainerMargin() {
@@ -636,7 +625,7 @@ export default class IndexController extends Controller {
this.formRowTarget.classList.remove('d-none');
this.journalsContainerTarget?.classList.add('work-packages-activities-tab-index-component--journals-container_with-input-compensation');
this.addEventListenersToCkEditorInstance();
this.addCkEditorEventListeners();
if (this.isMobile()) {
this.focusEditor(0);
@@ -670,7 +659,7 @@ export default class IndexController extends Controller {
hideEditor() {
this.clearEditor(); // remove potentially empty lines
this.removeEventListenersFromCkEditorInstance();
this.removeCkEditorEventListeners();
this.buttonRowTarget.classList.remove('d-none');
this.formRowTarget.classList.add('d-none');
@@ -2,6 +2,7 @@ import { ControllerConstructor } from '@hotwired/stimulus/dist/types/core/contro
import { ApplicationController } from 'stimulus-use';
import { AttributeObserver } from '@hotwired/stimulus';
import { debugLog } from 'core-app/shared/helpers/debug_output';
import { OpenProjectStimulusApplication } from 'core-stimulus/openproject-stimulus-application';
export class OpApplicationController extends ApplicationController {
private loaded = new Set<string>();
@@ -30,19 +31,43 @@ export class OpApplicationController extends ApplicationController {
const registered = this.application.router.modules.map((module) => module.definition.identifier);
controllers.forEach((controller) => {
const path = this.derivePath(controller);
if (!registered.includes(controller) && !this.loaded.has(controller)) {
debugLog(`Loading controller ${controller}`);
this.loaded.add(controller);
void import(/* webpackChunkName: "[request]" */`./dynamic/${path}.controller`)
.then((imported:{ default:ControllerConstructor }) => this.application.register(controller, imported.default))
.catch((err:unknown) => {
console.error('Failed to load dynamic controller chunk %O: %O', controller, err);
void this
.importController(controller)
.then((clazz) => {
if (clazz) {
this.application.register(controller, clazz);
}
});
}
});
}
private async importController(controller:string):Promise<ControllerConstructor|null> {
try {
const imported = await this.fetchDynamicController(controller);
return imported.default;
} catch (err) {
console.error('Failed to load dynamic controller chunk %O: %O', controller, err);
return null;
}
}
private async fetchDynamicController(controller:string) {
if (OpenProjectStimulusApplication.dynamicImports.has(controller)) {
return OpenProjectStimulusApplication.dynamicImports.get(controller)!();
}
// Default: Try to load the controller from dynamic/ subfolder.
const path = this.derivePath(controller);
return await import(/* webpackChunkName: "[request]" */ `./dynamic/${path}.controller`) as Promise<{
default:ControllerConstructor
}>;
}
/**
* Derive dynamic path from controller name.
*
@@ -0,0 +1,57 @@
import { ControllerConstructor } from '@hotwired/stimulus/dist/types/core/controller';
import { Application } from '@hotwired/stimulus';
export type DynamicControllerLoader = () => Promise<{ default:ControllerConstructor }>;
export class OpenProjectStimulusApplication extends Application {
/** A map of controllers that have been preregistered. */
static controllers = new Map<string, ControllerConstructor>();
/** A map of a dynamic controller loader to load when the controller name is found */
static dynamicImports = new Map<string, DynamicControllerLoader>();
/**
* Register a controller to be used in the application,
* allowing it to be registered before the Stimulus application is being initialized.
*
* This is useful for plugins that execute code before we call setup.ts
*
* @param name the name/identifier of the controller
* @param controller the controller class
*/
static preregister(name:string, controller:ControllerConstructor) {
this.controllers.set(name, controller);
}
/**
* Register a dynamic controller to be imported using the given path,
* allowing it to be defined somewhere else than within the dynamic/ subfolder.
*
* This is useful for plugins that want to define new dynamic controllers.
* How to use this: In your plugin's main.ts, call this
* @example
* OpenProjectStimulusApplication.preregisterDynamic(
* 'test',
* () => import('./test.controller')
* );
* ```
*
* @param name the name/identifier of the controller
* @param loader A callback to provide the controller asynchronously.
*/
static preregisterDynamic(name:string, loader:DynamicControllerLoader) {
this.dynamicImports.set(name, loader);
}
async start():Promise<void> {
this.preregisteredControllers.forEach((controller, name) => {
this.register(name, controller);
});
await super.start();
}
get preregisteredControllers() {
return OpenProjectStimulusApplication.controllers;
}
}
+25 -27
View File
@@ -1,4 +1,3 @@
import { Application } from '@hotwired/stimulus';
import { environment } from '../environments/environment';
import { OpApplicationController } from './controllers/op-application.controller';
import MainMenuController from './controllers/dynamic/menus/main.controller';
@@ -22,6 +21,8 @@ import ScrollIntoViewController from './controllers/scroll-into-view.controller'
import CkeditorFocusController from './controllers/ckeditor-focus.controller';
import AutoSubmit from '@stimulus-components/auto-submit';
import { OpenProjectStimulusApplication } from 'core-stimulus/openproject-stimulus-application';
import { Application } from '@hotwired/stimulus';
declare global {
interface Window {
@@ -29,35 +30,32 @@ declare global {
}
}
const instance = Application.start();
OpenProjectStimulusApplication.preregister('application', OpApplicationController);
OpenProjectStimulusApplication.preregister('async-dialog', AsyncDialogController);
OpenProjectStimulusApplication.preregister('disable-when-checked', OpDisableWhenCheckedController);
OpenProjectStimulusApplication.preregister('flash', FlashController);
OpenProjectStimulusApplication.preregister('menus--main', MainMenuController);
OpenProjectStimulusApplication.preregister('password-confirmation-dialog', PasswordConfirmationDialogController);
OpenProjectStimulusApplication.preregister('poll-for-changes', PollForChangesController);
OpenProjectStimulusApplication.preregister('print', PrintController);
OpenProjectStimulusApplication.preregister('refresh-on-form-changes', RefreshOnFormChangesController);
OpenProjectStimulusApplication.preregister('form-preview', FormPreviewController);
OpenProjectStimulusApplication.preregister('hover-card-trigger', HoverCardTriggerController);
OpenProjectStimulusApplication.preregister('show-when-checked', OpShowWhenCheckedController);
OpenProjectStimulusApplication.preregister('show-when-value-selected', OpShowWhenValueSelectedController);
OpenProjectStimulusApplication.preregister('table-highlighting', TableHighlightingController);
OpenProjectStimulusApplication.preregister('projects-zen-mode', OpProjectsZenModeController);
OpenProjectStimulusApplication.preregister('work-packages--date-picker--preview', PreviewController);
OpenProjectStimulusApplication.preregister('keep-scroll-position', KeepScrollPositionController);
OpenProjectStimulusApplication.preregister('pattern-input', PatternInputController);
OpenProjectStimulusApplication.preregister('scroll-into-view', ScrollIntoViewController);
OpenProjectStimulusApplication.preregister('ckeditor-focus', CkeditorFocusController);
OpenProjectStimulusApplication.preregister('auto-submit', AutoSubmit);
const instance = OpenProjectStimulusApplication.start();
window.Stimulus = instance;
instance.debug = !environment.production;
instance.handleError = (error, message, detail) => {
console.warn(error, message, detail);
};
instance.register('async-dialog', AsyncDialogController);
instance.register('disable-when-checked', OpDisableWhenCheckedController);
instance.register('flash', FlashController);
instance.register('menus--main', MainMenuController);
instance.register('password-confirmation-dialog', PasswordConfirmationDialogController);
instance.register('poll-for-changes', PollForChangesController);
instance.register('print', PrintController);
instance.register('refresh-on-form-changes', RefreshOnFormChangesController);
instance.register('form-preview', FormPreviewController);
instance.register('hover-card-trigger', HoverCardTriggerController);
instance.register('show-when-checked', OpShowWhenCheckedController);
instance.register('show-when-value-selected', OpShowWhenValueSelectedController);
instance.register('table-highlighting', TableHighlightingController);
instance.register('projects-zen-mode', OpProjectsZenModeController);
instance.register('work-packages--date-picker--preview', PreviewController);
instance.register('keep-scroll-position', KeepScrollPositionController);
instance.register('pattern-input', PatternInputController);
instance.register('scroll-into-view', ScrollIntoViewController);
instance.register('ckeditor-focus', CkeditorFocusController);
instance.register('auto-submit', AutoSubmit);
// Application controller must be registered last, as it tries to automatically load other controllers
// not yet registered.
instance.register('application', OpApplicationController);
+5
View File
@@ -10,6 +10,7 @@ import { addTurboGlobalListeners } from './turbo-global-listeners';
import { applyTurboNavigationPatch } from './turbo-navigation-patch';
import { debugLog, whenDebugging } from 'core-app/shared/helpers/debug_output';
import { TURBO_EVENTS } from './constants';
import { StreamActions } from '@hotwired/turbo';
Turbo.session.drive = true;
Turbo.setProgressBarDelay(100);
@@ -34,6 +35,10 @@ registerFlashStreamAction();
registerLiveRegionStreamAction();
registerInputCaptionStreamAction();
StreamActions.reloadPage = function reloadPage() {
window.location.reload();
};
// Apply navigational patch
// https://github.com/hotwired/turbo/issues/1300
applyTurboNavigationPatch();
+1 -1
View File
@@ -35,7 +35,7 @@ module API
code 500
def initialize(message = I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 403))
super
super(message || I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 403))
end
end
end
+1 -1
View File
@@ -35,7 +35,7 @@ module API
code 500
def initialize(message = I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 404))
super
super(message || I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 404))
end
end
end
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -33,7 +35,7 @@ module API
code 401
def initialize(message = I18n.t("api_v3.errors.code_401"))
super
super(message || I18n.t("api_v3.errors.code_401"))
end
end
end
@@ -37,7 +37,7 @@ module API
end
def href_callback
query_params = "sortBy=#{to_query [%i(semver_name asc)]}&pageSize=-1"
query_params = "sortBy=#{to_query [%i(name asc)]}&pageSize=-1"
if filter.project.nil?
"#{api_v3_paths.versions}?#{query_params}"
+1 -1
View File
@@ -57,7 +57,7 @@ module FullCalendar
"title" => title,
"url" => url,
"classNames" => class_names
}.merge(additional_attributes).compact_blank.as_json
}.merge(additional_attributes).compact.as_json
end
def to_json(*)
@@ -1,42 +0,0 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
#
module OpenProject::Patches::SecureHeadersTurboAwareNonce
def content_security_policy_script_nonce(request)
if request.env["HTTP_TURBO_REFERRER"].present?
request.env[SecureHeaders::NONCE_KEY] ||= request.env["HTTP_X_TURBO_NONCE"]
end
super
end
end
OpenProject::Patches.patch_gem_version "secure_headers", "7.1.0" do
SecureHeaders.singleton_class.prepend OpenProject::Patches::SecureHeadersTurboAwareNonce
end

Some files were not shown because too many files have changed in this diff Show More