diff --git a/Gemfile b/Gemfile index 366d19ea5233..f8e962882816 100644 --- a/Gemfile +++ b/Gemfile @@ -61,7 +61,7 @@ gem "friendly_id", "~> 5.5.0" gem "acts_as_list", "~> 1.2.0" gem "acts_as_tree", "~> 2.9.0" -gem "awesome_nested_set", "~> 3.7.0" +gem "awesome_nested_set", "~> 3.8.0" gem "closure_tree", "~> 7.4.0" gem "rubytree", "~> 2.1.0" # Only used in down migrations now. @@ -140,7 +140,7 @@ gem "rack-attack", "~> 6.7.0" gem "secure_headers", "~> 7.0.0" # Browser detection for incompatibility checks -gem "browser", "~> 6.0.0" +gem "browser", "~> 6.1.0" # Providing health checks gem "okcomputer", "~> 1.18.1" diff --git a/Gemfile.lock b/Gemfile.lock index fa36feac69ac..b58762173e66 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,7 +206,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (~> 1.90.0) + aws-sdk-sns (~> 1.92.0) messagebird-rest (~> 1.4.2) rotp (~> 6.1) webauthn (~> 3.0) @@ -341,10 +341,10 @@ GEM attr_required (1.0.2) auto_strip_attributes (2.6.0) activerecord (>= 4.0) - awesome_nested_set (3.7.0) - activerecord (>= 4.0.0, < 8.0) + awesome_nested_set (3.8.0) + activerecord (>= 4.0.0, < 8.1) aws-eventstream (1.3.0) - aws-partitions (1.1015.0) + aws-partitions (1.1017.0) aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -353,11 +353,11 @@ GEM aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.175.0) + aws-sdk-s3 (1.176.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-sns (1.90.0) + aws-sdk-sns (1.92.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) aws-sigv4 (1.10.1) @@ -391,7 +391,7 @@ GEM msgpack (~> 1.2) brakeman (6.2.2) racc - browser (6.0.0) + browser (6.1.0) builder (3.3.0) byebug (11.1.3) capybara (3.40.0) @@ -449,7 +449,7 @@ GEM ferrum (~> 0.15.0) daemons (1.4.1) dalli (3.2.8) - date (3.4.0) + date (3.4.1) date_validator (0.12.0) activemodel (>= 3) activesupport (>= 3) @@ -545,20 +545,20 @@ GEM tzinfo eventmachine (1.2.7) eventmachine_httpserver (0.2.1) - excon (1.2.0) + excon (1.2.2) factory_bot (6.5.0) activesupport (>= 5.0.0) factory_bot_rails (6.4.4) factory_bot (~> 6.5) railties (>= 5.0.0) - faraday (2.12.0) - faraday-net_http (>= 2.0, < 3.4) + faraday (2.12.1) + faraday-net_http (>= 2.0, < 3.5) json logger faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.3.0) - net-http + faraday-net_http (3.4.0) + net-http (>= 0.5.0) fastimage (2.3.1) ferrum (0.15) addressable (~> 2.5) @@ -681,7 +681,7 @@ GEM reline (>= 0.4.2) iso8601 (0.13.0) jmespath (1.6.2) - json (2.8.2) + json (2.9.0) json-jwt (1.16.7) activesupport (>= 4.2) aes_key_wrap @@ -707,7 +707,7 @@ GEM launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) - lefthook (1.8.4) + lefthook (1.8.5) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) @@ -722,7 +722,7 @@ GEM omniauth (~> 1.1) omniauth-openid-connect (>= 0.2.1) rails (>= 3.2.21) - logger (1.6.1) + logger (1.6.2) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -762,7 +762,7 @@ GEM mini_magick (5.0.1) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.1) + minitest (5.25.2) msgpack (1.7.5) multi_json (1.15.0) mustermann (3.0.3) @@ -770,7 +770,7 @@ GEM mustermann-grape (1.1.0) mustermann (>= 1.0.0) mutex_m (0.3.0) - net-http (0.4.1) + net-http (0.6.0) uri net-imap (0.5.1) date @@ -783,7 +783,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.16.7) + nokogiri (1.16.8) mini_portile2 (~> 2.8.2) racc (~> 1.4) oj (3.16.7) @@ -868,7 +868,8 @@ GEM pry-rescue (1.6.0) interception (>= 0.5) pry (>= 0.12.0) - psych (5.2.0) + psych (5.2.1) + date stringio public_suffix (6.0.1) puffing-billy (4.0.0) @@ -935,9 +936,9 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (7.0.10) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -954,7 +955,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rb_sys (0.9.102) + rb_sys (0.9.103) rbtrace (0.5.1) ffi (>= 1.0.6) msgpack (>= 0.4.3) @@ -968,8 +969,8 @@ GEM redis-client (>= 0.22.0) redis-client (0.22.2) connection_pool - regexp_parser (2.9.2) - reline (0.5.11) + regexp_parser (2.9.3) + reline (0.5.12) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -1012,14 +1013,14 @@ GEM rspec-support (3.13.1) rspec-wait (1.0.1) rspec (>= 3.4) - rubocop (1.69.0) + rubocop (1.69.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.36.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.36.2) @@ -1064,7 +1065,7 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.12.0) secure_headers (7.0.0) - securerandom (0.3.2) + securerandom (0.4.0) selenium-devtools (0.131.0) selenium-webdriver (~> 4.2) selenium-webdriver (4.27.0) @@ -1143,7 +1144,7 @@ GEM tzinfo (>= 1.0.0) uber (0.1.0) unicode-display_width (2.6.0) - uri (0.13.1) + uri (1.0.2) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -1181,7 +1182,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.0) + webrick (1.9.1) websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -1210,14 +1211,14 @@ DEPENDENCIES airbrake (~> 13.0.0) appsignal (~> 3.10.0) auto_strip_attributes (~> 2.5) - awesome_nested_set (~> 3.7.0) + awesome_nested_set (~> 3.8.0) aws-sdk-core (~> 3.107) aws-sdk-s3 (~> 1.91) axe-core-rspec bcrypt (~> 3.1.6) bootsnap (~> 1.18.0) brakeman (~> 6.2.0) - browser (~> 6.0.0) + browser (~> 6.1.0) budgets! capybara (~> 3.40.0) capybara-screenshot (~> 1.0.17) diff --git a/app/assets/images/lookbook/hover_card.png b/app/assets/images/lookbook/hover_card.png index 8dde8feb902b..f2389dd7ded0 100644 Binary files a/app/assets/images/lookbook/hover_card.png and b/app/assets/images/lookbook/hover_card.png differ diff --git a/app/assets/images/lookbook/user_hover_card.png b/app/assets/images/lookbook/user_hover_card.png new file mode 100644 index 000000000000..b225bcfad038 Binary files /dev/null and b/app/assets/images/lookbook/user_hover_card.png differ diff --git a/app/components/_index.sass b/app/components/_index.sass index 97caf1e6b6ee..aa9da94c01f9 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -19,3 +19,4 @@ @import "op_primer/border_box_table_component" @import "work_packages/exports/modal_dialog_component" @import "work_package_relations_tab/index_component" +@import "users/hover_card_component" diff --git a/app/components/shares/invite_user_form_component.html.erb b/app/components/shares/invite_user_form_component.html.erb index dea952c59aa6..f1bc280c58b9 100644 --- a/app/components/shares/invite_user_form_component.html.erb +++ b/app/components/shares/invite_user_form_component.html.erb @@ -10,7 +10,7 @@ ) do |form| grid_layout('invite-user-form', tag: :div) do |invite_form| invite_form.with_area('invitee') do - render(Shares::Invitee.new(form)) + render(Shares::Invitee.new(form, allow_hover_cards:)) end invite_form.with_area('permission') do diff --git a/app/components/shares/invite_user_form_component.rb b/app/components/shares/invite_user_form_component.rb index f8d40024bae9..07f5559506f2 100644 --- a/app/components/shares/invite_user_form_component.rb +++ b/app/components/shares/invite_user_form_component.rb @@ -32,14 +32,15 @@ class InviteUserFormComponent < ApplicationComponent # rubocop:disable OpenProje include OpTurbo::Streamable include OpPrimer::ComponentHelpers - attr_reader :entity, :strategy, :errors + attr_reader :entity, :strategy, :errors, :allow_hover_cards - def initialize(strategy:, errors: nil) + def initialize(strategy:, errors: nil, allow_hover_cards: false) super @strategy = strategy @entity = strategy.entity @errors = errors + @allow_hover_cards = allow_hover_cards end def new_share diff --git a/app/components/shares/manage_shares_component.html.erb b/app/components/shares/manage_shares_component.html.erb index 9cca5dea7e57..af18bb5f4229 100644 --- a/app/components/shares/manage_shares_component.html.erb +++ b/app/components/shares/manage_shares_component.html.erb @@ -1,7 +1,7 @@ <%= if strategy.manageable? modal_content.with_row do - render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) + render(Shares::InviteUserFormComponent.new(strategy:, errors:, allow_hover_cards:)) end end @@ -100,10 +100,15 @@ end else strategy.shares.each do |share| - render(Shares::ShareRowComponent.new(share:, strategy:, container: border_box)) + render(Shares::ShareRowComponent.new(share:, strategy:, container: border_box, allow_hover_cards:)) end end end end + if allow_hover_cards + modal_content.with_row do + helpers.angular_component_tag 'opce-custom-modal-overlay', class: 'op-user-share-modal-overlay' + end + end %> diff --git a/app/components/shares/manage_shares_component.rb b/app/components/shares/manage_shares_component.rb index fa61c6afdd57..22246aed01a3 100644 --- a/app/components/shares/manage_shares_component.rb +++ b/app/components/shares/manage_shares_component.rb @@ -36,6 +36,7 @@ class ManageSharesComponent < ApplicationComponent # rubocop:disable OpenProject attr_reader :strategy, :entity, :errors, + :allow_hover_cards, :modal_content def initialize(strategy:, modal_content:, errors: nil) @@ -45,6 +46,7 @@ def initialize(strategy:, modal_content:, errors: nil) @entity = strategy.entity @errors = errors @modal_content = modal_content + @allow_hover_cards = strategy.allow_hover_cards? end def self.wrapper_key diff --git a/app/components/shares/share_row_component.html.erb b/app/components/shares/share_row_component.html.erb index b7045299e475..47c931db3e76 100644 --- a/app/components/shares/share_row_component.html.erb +++ b/app/components/shares/share_row_component.html.erb @@ -13,7 +13,8 @@ end user_row_grid.with_area(:avatar, tag: :div) do - render(Users::AvatarComponent.new(user: principal, show_name: false, size: :medium)) + render(Users::AvatarComponent.new(user: principal, show_name: false, size: :medium, + hover_card: { active: allow_hover_cards, target: :custom })) end user_row_grid.with_area(:user_details, tag: :div, classes: 'ellipsis') do diff --git a/app/components/shares/share_row_component.rb b/app/components/shares/share_row_component.rb index 45df3ab20e0b..ae3b98457683 100644 --- a/app/components/shares/share_row_component.rb +++ b/app/components/shares/share_row_component.rb @@ -36,7 +36,7 @@ class ShareRowComponent < ApplicationComponent # rubocop:disable OpenProject/Add include OpTurbo::Streamable include OpPrimer::ComponentHelpers - def initialize(share:, strategy:, container: nil) + def initialize(share:, strategy:, container: nil, allow_hover_cards: false) super @share = share @@ -45,6 +45,7 @@ def initialize(share:, strategy:, container: nil) @principal = share.principal @available_roles = strategy.available_roles @container = container + @allow_hover_cards = allow_hover_cards end def wrapper_uniq_by @@ -53,7 +54,7 @@ def wrapper_uniq_by private - attr_reader :share, :entity, :principal, :container, :available_roles, :strategy + attr_reader :share, :entity, :principal, :container, :available_roles, :strategy, :allow_hover_cards def share_editable? @share_editable ||= User.current != share.principal && sharing_manageable? diff --git a/app/components/users/avatar_component.rb b/app/components/users/avatar_component.rb index 907f226ce736..c0c84ed78e65 100644 --- a/app/components/users/avatar_component.rb +++ b/app/components/users/avatar_component.rb @@ -32,7 +32,8 @@ class AvatarComponent < ApplicationComponent include AvatarHelper include OpPrimer::ComponentHelpers - def initialize(user:, show_name: true, link: true, size: "default", classes: "", title: nil, name_classes: "") + def initialize(user:, show_name: true, link: true, size: "default", classes: "", title: nil, name_classes: "", + hover_card: { active: true, target: :default }) super @user = user @@ -40,6 +41,7 @@ def initialize(user:, show_name: true, link: true, size: "default", classes: "", @link = link @size = size @title = title + @hover_card = hover_card @classes = classes @name_classes = name_classes end @@ -49,14 +51,19 @@ def render? end def call - helpers.avatar( - @user, + options = { size: @size, link: @link, hide_name: !@show_name, title: @title, class: @classes, - name_classes: @name_classes + name_classes: @name_classes, + hover_card: @hover_card + } + + helpers.avatar( + @user, + **options ) end end diff --git a/app/components/users/hover_card_component.html.erb b/app/components/users/hover_card_component.html.erb new file mode 100644 index 000000000000..7af47f81965e --- /dev/null +++ b/app/components/users/hover_card_component.html.erb @@ -0,0 +1,86 @@ +<%#-- 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. + +++#%> + +<%= + if @user.present? + flex_layout(classes: 'op-user-hover-card', data: { test_selector: "user-hover-card-#{@user.id}" }) do |flex| + flex.with_row do + render(Users::AvatarComponent.new(user: @user, show_name: false, link: false, hover_card: { active: false })) + end + + flex.with_row do + flex_layout(classes: 'op-user-hover-card--info') do |f| + f.with_column(classes: 'op-user-hover-card--name') do + render(Primer::Beta::Text.new(font_weight: :semibold, data: { test_selector: 'user-hover-card-name' })) do + @user.name + end + end + + if show_email? + f.with_column(classes: 'op-user-hover-card--email') do + render(Primer::Beta::Text.new(font_size: :small, + color: :muted, + data: { test_selector: 'user-hover-card-email' })) do + @user.mail + end + end + end + end + end + + flex.with_row do + flex_layout(classes: 'op-user-hover-card--group-list') do |f| + f.with_column do + render(Primer::Beta::Octicon.new(icon: :people)) + end + + f.with_column do + render(Primer::Beta::Text.new(color: :muted, data: { test_selector: 'user-hover-card-groups' })) do + group_membership_summary + end + end + end + end + + flex.with_row do + render(Primer::Beta::Button.new(tag: :a, + href: helpers.allowed_management_user_profile_path(@user), + data: { test_selector: 'user-hover-card-profile-btn' })) do + I18n.t("users.open_profile") + end + end + end + else + render Primer::Beta::Blankslate.new(border: false, narrow: true) do |component| + component.with_visual_icon(icon: "x-circle") + # Show a generic error message to avoid leaking information + component.with_heading(tag: :h3).with_content(I18n.t("http.response.unexpected")) + end + end +%> diff --git a/app/components/users/hover_card_component.rb b/app/components/users/hover_card_component.rb new file mode 100644 index 000000000000..6afd44af19b2 --- /dev/null +++ b/app/components/users/hover_card_component.rb @@ -0,0 +1,95 @@ +# 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 Users::HoverCardComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def initialize(id:) + super + + @id = id + @user = User.find_by(id: @id) + end + + def show_email? + (@user == User.current) || User.current.allowed_globally?(:view_user_email) + end + + # Constructs a string in the form of: + # "Member of group4, group5" + # or + # "Member of group1, group2 and 3 more" + # The latter string is cut off since the complete list of group names would exceed the allowed `max_length`. + def group_membership_summary(max_length = 40) + groups = @user.groups.visible + return no_group_text if groups.empty? + + group_links = linked_group_names(groups) + + cutoff_index = calculate_cutoff_index(groups.map(&:name), max_length) + build_summary(group_links, cutoff_index) + end + + private + + def linked_group_names(groups) + groups.map { |group| link_to(h(group.name), show_group_path(group)) } + end + + def no_group_text + t("users.groups.no_results_title_text") + end + + # Calculate the index at which to cut off the group names, based on plain text length + def calculate_cutoff_index(names, max_length) + current_length = 0 + + names.each_with_index do |name, index| + new_length = current_length + name.length + (index > 0 ? 2 : 0) # 2 for ", " separator + return index if new_length > max_length + + current_length = new_length + end + + names.size # No cutoff needed -> return the total size + end + + def build_summary(links, cutoff_index) + summary_links = safe_join(links[0...cutoff_index], ", ") + remaining_count = links.size - cutoff_index + remaining_count_link = link_to(t("users.groups.more", count: remaining_count), user_path(@user)) + + if remaining_count > 0 + t("users.groups.summary_with_more", names: summary_links, count_link: remaining_count_link).html_safe + else + t("users.groups.summary", names: summary_links).html_safe + end + end +end diff --git a/app/components/users/hover_card_component.sass b/app/components/users/hover_card_component.sass new file mode 100644 index 000000000000..ca6fa63034aa --- /dev/null +++ b/app/components/users/hover_card_component.sass @@ -0,0 +1,24 @@ +// Correct the z-index of the regular hover card container so that it is above the dropdown of user auto completers +.spot-modal-overlay:has(.op-user-hover-card) + z-index: 9600 + +// On the project list page, there is a bug when a hover card is invoked within the share dialog. The global spot +// modal overlay will darken the background while the hover card is active, since its semi-transparent bg shading is +// added on top of the other dialog background shaders. We don't want an additional spot modal background here, +// so we disable it for this edge case. +.controller-projects.action-index + .spot-modal-overlay:not(:has(.op-user-hover-card)) + background: transparent + +.op-user-hover-card + gap: 1rem + overflow: hidden + + &--info + gap: 0.5rem + + &--name, &--email + @include text-shortener() + + &--group-list + gap: 0.5rem diff --git a/app/components/work_package_relations_tab/add_work_package_child_form_component.html.erb b/app/components/work_package_relations_tab/add_work_package_child_form_component.html.erb index 17142756557b..cd3345e9a12a 100644 --- a/app/components/work_package_relations_tab/add_work_package_child_form_component.html.erb +++ b/app/components/work_package_relations_tab/add_work_package_child_form_component.html.erb @@ -16,6 +16,9 @@ end end flex.with_row do + # @workpackage is not available inside the render_inline_form block + # so we need to re-define them here. Figure out solution for this. + url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(@work_package.id, type: Relation::TYPE_CHILD) render_inline_form(f) do |my_form| my_form.work_package_autocompleter( name: :id, @@ -24,6 +27,8 @@ autocomplete_options: { resource: 'work_packages', searchKey: 'subjectOrId', + url:, + relations: true, # Activates relations fetch mode in the autocomplete openDirectly: false, focusDirectly: true, dropdownPosition: 'bottom', diff --git a/app/components/work_package_relations_tab/relation_component.html.erb b/app/components/work_package_relations_tab/relation_component.html.erb index 12788bdb76d0..b4cdd78822b4 100644 --- a/app/components/work_package_relations_tab/relation_component.html.erb +++ b/app/components/work_package_relations_tab/relation_component.html.erb @@ -58,20 +58,44 @@ flex_layout do |flex| end end - if should_display_start_and_end_dates? - flex.with_row(flex_layout: true, align_items: :center, mb: 2) do |start_and_end_dates_row| - start_and_end_dates_row.with_column(mr: 1) do - icon = if follows? - :calendar - elsif precedes? - :pin - end + if should_display_dates_row? + flex.with_row(flex_layout: true, align_items: :center, color: :muted, mb: 2) do |dates_row| + if precedes? && lag_present? + dates_row.with_column(mr: 1) do + render(Primer::Beta::Octicon.new(icon: "arrow-both")) + end + dates_row.with_column(mr: 3) do + render(Primer::Beta::Text.new) do + lag_as_text(relation.lag) + end + end + end + + if related_work_package.start_date.present? || related_work_package.due_date.present? + dates_row.with_column(mr: 1) do + icon = if follows? + :calendar + elsif precedes? + :pin + end - render(Primer::Beta::Octicon.new(icon:, color: :muted)) + render(Primer::Beta::Octicon.new(icon:)) + end + dates_row.with_column do + render(Primer::Beta::Text.new) do + "#{format_date(related_work_package.start_date)} - #{format_date(related_work_package.due_date)}" + end + end end - start_and_end_dates_row.with_column do - render(Primer::Beta::Text.new(color: :muted)) do - "#{format_date(related_work_package.start_date)} - #{format_date(related_work_package.due_date)}" + + if follows? && lag_present? + dates_row.with_column(ml: 3, mr: 1) do + render(Primer::Beta::Octicon.new(icon: "arrow-both")) + end + dates_row.with_column(mr: 1) do + render(Primer::Beta::Text.new) do + lag_as_text(relation.lag) + end end end end diff --git a/app/components/work_package_relations_tab/relation_component.rb b/app/components/work_package_relations_tab/relation_component.rb index efed86f893f4..08e1452e02c5 100644 --- a/app/components/work_package_relations_tab/relation_component.rb +++ b/app/components/work_package_relations_tab/relation_component.rb @@ -61,7 +61,11 @@ def should_display_description? relation.description.present? end - def should_display_start_and_end_dates? + def lag_present? + relation.lag.present? && relation.lag != 0 + end + + def should_display_dates_row? return false if parent_child_relationship? relation.follows? || relation.precedes? @@ -91,6 +95,10 @@ def destroy_path end end + def lag_as_text(lag) + "#{I18n.t('work_package_relations_tab.lag.subject')}: #{I18n.t('datetime.distance_in_words.x_days', count: lag)}" + end + def action_menu_test_selector "op-relation-row-#{underlying_resource_id}-action-menu" end diff --git a/app/components/work_package_relations_tab/work_package_relation_form_component.html.erb b/app/components/work_package_relations_tab/work_package_relation_form_component.html.erb index 0a6f63688a42..83879e8a97c6 100644 --- a/app/components/work_package_relations_tab/work_package_relation_form_component.html.erb +++ b/app/components/work_package_relations_tab/work_package_relation_form_component.html.erb @@ -19,6 +19,7 @@ # These are not available inside the render_inline_form block # so we need to re-define them here. Figure out solution for this. relation = @relation + lag_shown = show_lag? to_id_field_value = relation.to.present? ? "#{related_work_package.type.name.upcase} ##{related_work_package.id} - #{related_work_package.subject}" : nil url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(@work_package.id, type: relation.relation_type_for(@work_package)) render_inline_form(f) do |my_form| @@ -58,6 +59,17 @@ label: Relation.human_attribute_name(:description), autofocus: relation.persisted? ) + + if lag_shown + my_form.text_field( + name: :lag, + type: :number, + min: 0, + label: I18n.t("work_package_relations_tab.lag.title"), + caption: I18n.t("work_package_relations_tab.lag.caption"), + input_width: :small, + ) + end end end end %> diff --git a/app/components/work_package_relations_tab/work_package_relation_form_component.rb b/app/components/work_package_relations_tab/work_package_relation_form_component.rb index 938ce175ddf6..264d08770fab 100644 --- a/app/components/work_package_relations_tab/work_package_relation_form_component.rb +++ b/app/components/work_package_relations_tab/work_package_relation_form_component.rb @@ -64,4 +64,8 @@ def submit_url_options url: work_package_relations_path(@work_package) } end end + + def show_lag? + @relation.relation_type == Relation::TYPE_PRECEDES || @relation.relation_type == Relation::TYPE_FOLLOWS + end end diff --git a/app/components/work_packages/activities_tab/index_component.rb b/app/components/work_packages/activities_tab/index_component.rb index 4b80371b21f3..3be97f3d5cd0 100644 --- a/app/components/work_packages/activities_tab/index_component.rb +++ b/app/components/work_packages/activities_tab/index_component.rb @@ -78,7 +78,7 @@ def polling_interval end def adding_comment_allowed? - User.current.allowed_in_project?(:add_work_package_notes, @work_package.project) + User.current.allowed_in_work_package?(:add_work_package_notes, @work_package) end end end diff --git a/app/controllers/users/hover_card_controller.rb b/app/controllers/users/hover_card_controller.rb new file mode 100644 index 000000000000..714c09a96589 --- /dev/null +++ b/app/controllers/users/hover_card_controller.rb @@ -0,0 +1,37 @@ +# 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 Users::HoverCardController < ApplicationController + no_authorization_required! :show + + def show + @id = params[:id] + render layout: nil + end +end diff --git a/app/controllers/work_package_relations_controller.rb b/app/controllers/work_package_relations_controller.rb index 837e4e10a190..38095515fae5 100644 --- a/app/controllers/work_package_relations_controller.rb +++ b/app/controllers/work_package_relations_controller.rb @@ -121,12 +121,12 @@ def set_relation def create_relation_params params.require(:relation) - .permit(:relation_type, :to_id, :description) + .permit(:relation_type, :to_id, :description, :lag) .merge(from_id: @work_package.id) end def update_relation_params params.require(:relation) - .permit(:description) + .permit(:description, :lag) end end diff --git a/app/forms/shares/invitee.rb b/app/forms/shares/invitee.rb index e8eb8f8407d9..c60ed79d1c3c 100644 --- a/app/forms/shares/invitee.rb +++ b/app/forms/shares/invitee.rb @@ -51,14 +51,17 @@ class Invitee < ApplicationForm multiple: true, focusDirectly: true, appendToComponent: true, - disabled: @disabled + disabled: @disabled, + isOpenedInModal: true, + hoverCards: @allow_hover_cards } ) end - def initialize(disabled: false) + def initialize(disabled: false, allow_hover_cards: false) super() @disabled = disabled + @allow_hover_cards = allow_hover_cards end end end diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index bff01d94d896..25c00ff2e859 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -50,6 +50,10 @@ def manageable? raise NotImplementedError, "Override in a subclass and return true if the current user can manage sharing" end + def allow_hover_cards? + raise NotImplementedError, "Override in a subclass and return true if hover cards should appear hovering users" + end + def create_contract_class raise NotImplementedError, "Override in a subclass and return the contract class for creating a share" end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index eb05b1970d78..7a898438bb62 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -54,6 +54,10 @@ def viewable? @entity.visible? end + def allow_hover_cards? + true + end + def create_contract_class Shares::ProjectQueries::CreateContract end diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb index 556767dc08aa..018c6b0f5c71 100644 --- a/app/models/sharing_strategies/work_package_strategy.rb +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -53,6 +53,13 @@ def viewable? user.allowed_in_project?(:view_shared_work_packages, @entity.project) end + # Since the work package share dialog is embedded into an angular page, hover cards would compete for the + # portal outlet when rendering, causing bugs. Until the work package share dialog is refactored to be an + # async-dialog, we must disable hover cards for it. + def allow_hover_cards? + false + end + def share_description(share) # rubocop:disable Metrics/PerceivedComplexity,Metrics/AbcSize scope = %i[sharing user_details] diff --git a/app/models/work_package_custom_field.rb b/app/models/work_package_custom_field.rb index 0c1776abe2ad..5af119827e68 100644 --- a/app/models/work_package_custom_field.rb +++ b/app/models/work_package_custom_field.rb @@ -51,7 +51,7 @@ class WorkPackageCustomField < CustomField scope :usable_as_custom_action, -> { where.not(field_format: %w[hierarchy]) - order(:name) + .order(:name) } def self.summable diff --git a/app/views/custom_fields/_form.html.erb b/app/views/custom_fields/_form.html.erb index c8328bf81d6c..aa06e6d44711 100644 --- a/app/views/custom_fields/_form.html.erb +++ b/app/views/custom_fields/_form.html.erb @@ -100,7 +100,10 @@ See COPYRIGHT and LICENSE files for more details. <% if @custom_field.new_record? || @custom_field.multi_value_possible? %>
> <%= f.check_box :multi_value, - data: { action: 'admin--custom-fields#checkOnlyOne' } %> + data: { action: "admin--custom-fields#checkOnlyOne" } %> +
+

<%= t("custom_fields.instructions.multi_select") %>

+
> diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 968d66ba0867..0ac67a4077d0 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -58,9 +58,9 @@ See COPYRIGHT and LICENSE files for more details. <% if @newss.any? %> <% @newss.each do |news| %>
-

<%= avatar(news.author) %><%= link_to_project(news.project) + ': ' unless news.project == @project %> - <%= link_to h(news.title), news_path(news) %> - <%= "(#{t(:label_x_comments, count: news.comments_count)})" if news.comments_count > 0 %>

+

<%= avatar(news.author) %><%= link_to_project(news.project) + ': ' unless news.project == @project %> + <%= link_to h(news.title), news_path(news) %> + <%= "(#{t(:label_x_comments, count: news.comments_count)})" if news.comments_count > 0 %>

<%= authoring news.created_at, news.author %>

<%= format_text(news.summary.present? ? news.summary : truncate(news.description, length: 150, escape: false), object: news) %> diff --git a/app/views/users/hover_card/show.html.erb b/app/views/users/hover_card/show.html.erb new file mode 100644 index 000000000000..5fd0177bb0eb --- /dev/null +++ b/app/views/users/hover_card/show.html.erb @@ -0,0 +1,3 @@ + + <%= render Users::HoverCardComponent.new(id: @id) %> + diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index de1fd0964c60..561068297503 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -643,6 +643,10 @@ af: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index a0a404e3fa01..d768d0f2a17d 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -675,6 +675,10 @@ ar: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index 8243c829fe5c..56c8edfbfbe1 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -643,6 +643,10 @@ az: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 36a5480e7c0d..7786acb7b24b 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -659,6 +659,10 @@ be: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 16fe66e6ae2b..c5aaa7e3b9e7 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -643,6 +643,10 @@ bg: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index b7bb86d49e2a..1d0584d34647 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -639,6 +639,10 @@ ca: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 5c277918eac6..7b8bab060ad5 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -643,6 +643,10 @@ ckb-IR: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 11bcf84caaaf..08f2d2362d37 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -659,6 +659,10 @@ cs: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 80fdcdf95e9b..481e38a37b3f 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -641,6 +641,10 @@ da: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index f0ce8b5fb5d6..9283eaa294a2 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -636,6 +636,10 @@ de: label_add_x: "%{x} hinzufügen" label_edit_x: "%{x} editieren" label_add_description: "Beschreibung hinzufügen" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "Beziehung mit" label_relates_plural: "Beziehungen mit" diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 4f10230940bd..cf87142fc00b 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -639,6 +639,10 @@ el: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 0c3f35431547..b9d9443732fe 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -643,6 +643,10 @@ eo: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/es.seeders.yml b/config/locales/crowdin/es.seeders.yml index 802fcff4a1e8..3f973b6b9c97 100644 --- a/config/locales/crowdin/es.seeders.yml +++ b/config/locales/crowdin/es.seeders.yml @@ -36,15 +36,15 @@ es: name: Negro life_cycle_colors: item_0: - name: PM2 Orange + name: PM2 Naranja item_1: - name: PM2 Purple + name: PM2 Morado item_2: - name: PM2 Red + name: PM2 Rojo item_3: name: PM2 Magenta item_4: - name: PM2 Green Yellow + name: PM2 Verde amarillo document_categories: item_0: name: Documentación @@ -83,19 +83,19 @@ es: standard: life_cycles: item_0: - name: Initiating + name: Iniciando item_1: - name: Ready for Planning + name: Listo para la planificación item_2: - name: Planning + name: Planificación item_3: - name: Ready for Executing + name: Listo para la ejecución item_4: - name: Executing + name: Ejecutando item_5: - name: Ready for Closing + name: Listo para el cierre item_6: - name: Closing + name: Cierre priorities: item_0: name: Baja diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index fb159873968e..0cde7b7fd3a6 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -33,7 +33,7 @@ es: label_activity_show_only_changes: "Mostrar solo los cambios" label_sort_asc: "Lo más reciente en la parte inferior" label_sort_desc: "Lo más reciente en la parte superior" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Añada un comentario. Escriba @ para notificar a personas." label_submit_comment: "Enviar comentario" changed_on: "cambiado el" created_on: "creó esto el" @@ -237,7 +237,7 @@ es: one: 1 subelemento other: "%{count} subelementos" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "¿Necesita una jerarquía en sus campos personalizados para paquetes de trabajo?" text_add_new_custom_field: > Para agregar nuevos campos personalizados a un proyecto, primero debe crearlos, y luego añadirlos a este proyecto. is_enabled_globally: "Está activado a nivel global" @@ -264,20 +264,20 @@ es: single: "o bien" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "debe ser un entero." + filled?: "debe rellenarse." + greater_or_equal_zero: "debe ser mayor o igual a 0." + not_found: "no encontrado." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "no puede ser un elemento raíz." + not_persisted: "debe ser un elemento ya existente." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "debe ser único dentro del mismo nivel de jerarquía." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "debe ser único dentro del mismo nivel de jerarquía." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "debe ser descendiente de la raíz de la jerarquía." rules: depth: "Profundidad" item: "Elemento" @@ -633,64 +633,68 @@ es: no_results_title_text: No hay versiones disponibles por el momento. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Añada relaciones a otros paquetes de trabajo para crear un vínculo entre ellos." + no_results_title_text: No hay relaciones disponibles por el momento. + blankslate_heading: "Sin relaciones" + blankslate_description: "Este paquete de trabajo aún no tiene relaciones." + label_add_x: "Añadir %{x}" + label_edit_x: "Editar %{x}" + label_add_description: "Añadir descripción" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + label_relates_singular: "relacionado con" + label_relates_plural: "relacionado con" + label_relates_to_singular: "relacionado con" + label_relates_to_plural: "relacionado con" + relates_description: "Crea un vínculo visible entre los dos paquetes de trabajo sin ningún efecto adicional" + relates_to_description: "Crea un vínculo visible entre los dos paquetes de trabajo sin ningún efecto adicional" + label_precedes_singular: "sucesor (después)" + label_precedes_plural: "sucesores (después)" + precedes_description: "El paquete de trabajo relacionado tiene que empezar necesariamente después de que termine este" + label_follows_singular: "predecesor (antes)" + label_follows_plural: "predecesores (antes)" + follows_description: "El paquete de trabajo relacionado tiene que terminar necesariamente antes de que este pueda empezar" + label_child_singular: "secundario" + label_child_plural: "secundarios" + child_description: "Hace que el paquete de trabajo relacionado sea un subelemento del paquete de trabajo actual (principal)" + label_blocks_singular: "bloques" + label_blocks_plural: "bloques" + blocks_description: "El paquete de trabajo relacionado no puede cerrarse hasta que este se cierre primero" + label_blocked_singular: "bloqueado por" + label_blocked_plural: "bloqueado por" + label_blocked_by_singular: "bloqueado por" + label_blocked__by_plural: "bloqueado por" + blocked_description: "Este paquete de trabajo no puede cerrarse hasta que el relacionado se cierre primero" + blocked_by_description: "Este paquete de trabajo no puede cerrarse hasta que el relacionado se cierre primero" + label_duplicates_singular: "duplicados" + label_duplicates_plural: "duplicados" + duplicates_description: "Esta es una copia del paquete de trabajo relacionado" + label_duplicated_singular: "duplicado por" + label_duplicated_plural: "duplicado por" + label_duplicated_by_singular: "duplicado por" + label_duplicated_by_plural: "duplicado por" + duplicated_by_description: "El paquete de trabajo relacionado es una copia de este" + duplicated_description: "El paquete de trabajo relacionado es una copia de este" + label_includes_singular: "incluye" + label_includes_plural: "incluye" + includes_description: "Marca el paquete de trabajo relacionado como incluido este sin ningún efecto adicional" + label_partof_singular: "parte de" + label_partof_plural: "parte de" + label_part_of_singular: "parte de" + label_part_of_plural: "parte de" + partof_description: "Marca el paquete de trabajo relacionado como parte de este sin ningún efecto adicional" + part_of_description: "Marca el paquete de trabajo relacionado como parte de este sin ningún efecto adicional" + label_requires_singular: "requiere" + label_requires_plural: "requiere" + requires_description: "Marca el paquete de trabajo relacionado como requisito de este" + label_required_singular: "requerido por" + label_required_plural: "requerido por" + required_description: "Marca este paquete de trabajo como requisito del relacionado" + label_parent_singular: "principal" + label_parent_plural: "principal" label_invitation: Invitación account: delete: "Borrar cuenta" @@ -1090,17 +1094,17 @@ es: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "debe ser Project::StageDefinition o Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "debe ser Project::Stage o Project::Gate" + must_be_a_stage: "debe ser un Project::Stage" + must_be_a_gate: "debe ser un Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "No se puede asignar `end_date` a un Project::Gate" query: attributes: project: @@ -1897,22 +1901,22 @@ es: hours: h days: d pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Página %{page} de %{total}" dialog: - title: Generate PDF - submit: Generate + title: Generar PDF + submit: Generar header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: A la derecha del encabezado + caption: Texto que debe aparecer a la derecha del encabezado footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Centro del pie de página + caption: Texto que debe aparecer en el centro del pie de página hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Separación silábica + caption: Separa las palabras entre líneas para mejorar la justificación y legibilidad del texto. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Tamaño del papel + caption: El tamaño del papel a utilizar para el PDF. extraction: available: pdftotext: "Pdftotext disponible (opcional)" @@ -2271,7 +2275,7 @@ es: label_environment: "Entorno" label_estimates_and_progress: "Estimaciones y progreso" label_equals: "es" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "es cualquiera con descendientes" label_everywhere: "todo" label_example: "Ejemplo" label_experimental: "Experimental" @@ -2536,8 +2540,8 @@ es: label_related_work_packages: "Paquetes de trabajo relacionados" label_relates: "relacionado con" label_relates_to: "relacionado con" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "Relación" + label_relation_actions: "Acciones de relación" label_relation_delete: "Eliminar relación" label_relation_new: "Nueva relación" label_release_notes: "Notas de lanzamiento" diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 675a403bcc2c..f71b61f8ab6c 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -643,6 +643,10 @@ et: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index d55207d08642..a10049f17c00 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -643,6 +643,10 @@ eu: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 72e94acf9f01..171d859ac311 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -643,6 +643,10 @@ fa: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index 6a2f40f432fc..bc1a64a1650e 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -643,6 +643,10 @@ fi: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 089711d09815..94c09c71219e 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -643,6 +643,10 @@ fil: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/fr.seeders.yml b/config/locales/crowdin/fr.seeders.yml index bb76409aebb7..407225dde07b 100644 --- a/config/locales/crowdin/fr.seeders.yml +++ b/config/locales/crowdin/fr.seeders.yml @@ -36,15 +36,15 @@ fr: name: Noir life_cycle_colors: item_0: - name: PM2 Orange + name: Orange PM2 item_1: - name: PM2 Purple + name: Violet PM2 item_2: - name: PM2 Red + name: Rouge PM2 item_3: - name: PM2 Magenta + name: Magenta PM2 item_4: - name: PM2 Green Yellow + name: Jaune vert PM2 document_categories: item_0: name: Documentation @@ -83,19 +83,19 @@ fr: standard: life_cycles: item_0: - name: Initiating + name: Initiation item_1: - name: Ready for Planning + name: Prêt pour la planification item_2: - name: Planning + name: Planification item_3: - name: Ready for Executing + name: Prêt pour l'exécution item_4: - name: Executing + name: Exécution item_5: - name: Ready for Closing + name: Prêt pour la fermeture item_6: - name: Closing + name: Fermeture priorities: item_0: name: Basse diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index a03acb2c154b..1c8df6f1f403 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -33,7 +33,7 @@ fr: label_activity_show_only_changes: "Afficher uniquement les modifications" label_sort_asc: "Les plus récents en bas" label_sort_desc: "Les plus récents en haut" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Ajoutez un commentaire. Saisissez @ pour informer les gens." label_submit_comment: "Envoyer le commentaire" changed_on: "modifié le" created_on: "a créé ce lot de travaux le" @@ -240,7 +240,7 @@ fr: one: 1 sous-élément other: "%{count} sous-éléments" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Vous avez besoin d'une hiérarchie dans vos champs personnalisés pour les lots de travaux ?" text_add_new_custom_field: > Pour ajouter de nouveaux champs personnalisés à un projet, vous devez d’abord les créer avant de pouvoir les ajouter à ce projet. is_enabled_globally: "Est activé globalement" @@ -267,20 +267,20 @@ fr: single: "ou" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "doit être un nombre entier." + filled?: "doit être rempli." + greater_or_equal_zero: "doit être supérieur ou égal à 0." + not_found: "introuvable." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "ne peut pas être un élément racine." + not_persisted: "doit être un élément déjà existant." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "doit être unique au sein d'un même niveau hiérarchique." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "doit être unique au sein d'un même niveau hiérarchique." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "doit être un descendant de la racine de la hiérarchie." rules: depth: "Profondeur" item: "Élément" @@ -634,62 +634,66 @@ fr: no_results_title_text: Il n'y a actuellement aucune version disponible. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Ajoutez des relations à d'autres lots de travaux pour créer un lien entre eux." + no_results_title_text: Il n'y a actuellement aucune relation disponible. + blankslate_heading: "Aucune relation" + blankslate_description: "Ce lot de travaux n'a pas encore de relations." + label_add_x: "Ajouter %{x}" + label_edit_x: "Modifier %{x}" + label_add_description: "Ajouter une description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" + label_relates_singular: "lié à" + label_relates_plural: "liés à" + label_relates_to_singular: "lié à" + label_relates_to_plural: "liés à" + relates_description: "Crée un lien visible entre les deux lots de travaux sans effet supplémentaire" + relates_to_description: "Crée un lien visible entre les deux lots de travaux sans effet supplémentaire" + label_precedes_singular: "successeur (après)" + label_precedes_plural: "successeurs (après)" + precedes_description: "Le lot de travaux lié doit nécessairement commencer après la fin de celui-ci" + label_follows_singular: "prédécesseur (avant)" + label_follows_plural: "prédécesseurs (avant)" + follows_description: "Le lot de travaux lié doit nécessairement se terminer avant que celui-ci puisse commencer" + label_child_singular: "enfant" + label_child_plural: "enfants" + child_description: "Fait du lot de travaux apparenté un sous-élément du lot de travaux actuel (parent)" + label_blocks_singular: "blocs" + label_blocks_plural: "blocs" + blocks_description: "Le lot de travaux associé ne peut pas être fermé avant que celui-ci soit fermé en premier" + label_blocked_singular: "bloqué par" + label_blocked_plural: "bloqués par" + label_blocked_by_singular: "bloqué par" + label_blocked__by_plural: "bloqués par" + blocked_description: "Ce lot de travaux ne peut pas être fermé avant que le lot associé soit fermé en premier" + blocked_by_description: "Ce lot de travaux ne peut pas être fermé avant que le lot associé soit fermé en premier" + label_duplicates_singular: "doublon" + label_duplicates_plural: "doublons" + duplicates_description: "Il s'agit d'une copie du lot de travaux correspondant" + label_duplicated_singular: "dupliqué par" + label_duplicated_plural: "dupliqués par" + label_duplicated_by_singular: "dupliqué par" + label_duplicated_by_plural: "dupliqués par" + duplicated_by_description: "Le lot de travaux correspondant est une copie de ceci" + duplicated_description: "Le lot de travaux correspondant est une copie de ceci" + label_includes_singular: "inclut" + label_includes_plural: "inclut" + includes_description: "Marque le lot de travaux lié comme incluant celui-ci sans effet supplémentaire" + label_partof_singular: "partie de" + label_partof_plural: "partie de" + label_part_of_singular: "partie de" + label_part_of_plural: "partie de" + partof_description: "Marque le lot de travaux lié comme faisant partie de celui-ci sans effet supplémentaire" + part_of_description: "Marque le lot de travaux lié comme faisant partie de celui-ci sans effet supplémentaire" + label_requires_singular: "requiert" + label_requires_plural: "requiert" + requires_description: "Marque le lot de travaux lié comme obligatoire pour celui-ci" + label_required_singular: "requis par" + label_required_plural: "requis par" + required_description: "Marque ce lot de travaux comme étant une exigence pour le lot de travaux correspondant" label_parent_singular: "parent" label_parent_plural: "parent" label_invitation: Invitation @@ -1091,17 +1095,17 @@ fr: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "doit être soit Project::StageDefinition soit Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "doit être soit Project::Stage soit Project::Gate" + must_be_a_stage: "doit être un Project::Stage" + must_be_a_gate: "doit être un Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "Impossible d'affecter `end_date` à un Project::Gate" query: attributes: project: @@ -1898,22 +1902,22 @@ fr: hours: h days: j pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Page %{page} sur %{total}" dialog: - title: Generate PDF - submit: Generate + title: Générer un fichier PDF + submit: Générer header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: En-tête droit + caption: Texte à afficher à droite de l'en-tête footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Centre du pied de page + caption: Texte à afficher au centre du pied de page hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Division syllabique + caption: Séparez les mots entre les lignes pour améliorer la justification et la lisibilité du texte. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Format du papier + caption: La taille du papier à utiliser pour le PDF. extraction: available: pdftotext: "Pdftotext disponible (optionnel)" @@ -2272,7 +2276,7 @@ fr: label_environment: "Environement" label_estimates_and_progress: "Estimations et progression" label_equals: "est" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "est l'un des éléments avec des descendants" label_everywhere: "partout" label_example: "Exemple" label_experimental: "Expérimental" @@ -2538,7 +2542,7 @@ fr: label_relates: "En relation avec" label_relates_to: "En relation avec" label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation_actions: "Actions de relation" label_relation_delete: "Supprimer la relation" label_relation_new: "Nouvelle relation" label_release_notes: "Notes de version" diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index 68e63bdbb081..2106a274efcf 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -659,6 +659,10 @@ he: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index e7ab6f56f39d..f4e1908dc653 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -641,6 +641,10 @@ hi: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index 2759fb4b6387..6c7bdc390f4a 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -651,6 +651,10 @@ hr: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 3710cd6d857c..4ffb6d0d9acc 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -640,6 +640,10 @@ hu: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index ce5520c7f9b4..9a9e0076bc62 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -628,6 +628,10 @@ id: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/it.seeders.yml b/config/locales/crowdin/it.seeders.yml index 824c08feb711..72dc6d95e0ed 100644 --- a/config/locales/crowdin/it.seeders.yml +++ b/config/locales/crowdin/it.seeders.yml @@ -36,15 +36,15 @@ it: name: Nero life_cycle_colors: item_0: - name: PM2 Orange + name: PM2 Arancione item_1: - name: PM2 Purple + name: PM2 Viola item_2: - name: PM2 Red + name: PM2 Rosso item_3: name: PM2 Magenta item_4: - name: PM2 Green Yellow + name: PM2 Giallo Verde document_categories: item_0: name: Documentazione @@ -83,19 +83,19 @@ it: standard: life_cycles: item_0: - name: Initiating + name: Avviamento item_1: - name: Ready for Planning + name: Pronto per la pianificazione item_2: - name: Planning + name: Pianificazione item_3: - name: Ready for Executing + name: Pronto per l'esecuzione item_4: - name: Executing + name: Esecuzione item_5: - name: Ready for Closing + name: Pronto per la chiusura item_6: - name: Closing + name: Chiusura priorities: item_0: name: Basso diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 3bf1b421ba91..bd72b7e37ab4 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -33,7 +33,7 @@ it: label_activity_show_only_changes: "Mostra solo le modifiche" label_sort_asc: "Più recenti in fondo" label_sort_desc: "Più recenti in cima" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Aggiungi un commento. Digita @ per avvisare le persone." label_submit_comment: "Invia commento" changed_on: "ha modificato il" created_on: "l'ha creata il" @@ -237,7 +237,7 @@ it: one: 1 sottoelemento other: "%{count} sottoelementi" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Hai bisogno di una gerarchia nei campi personalizzati per le macro-attività?" text_add_new_custom_field: > Per aggiungere nuovi campi personalizzati a un progetto, è necessario prima crearli. is_enabled_globally: "Abilitato a livello globale" @@ -264,20 +264,20 @@ it: single: "oppure" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "deve essere un intero." + filled?: "deve essere compilato." + greater_or_equal_zero: "deve essere maggiore o uguale a 0." + not_found: "non trovato." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "non può essere un elemento radice." + not_persisted: "deve essere un elemento già esistente." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "deve essere unico all'interno dello stesso livello gerarchico." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "deve essere unico all'interno dello stesso livello gerarchico." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "deve essere un discendente della radice gerarchica." rules: depth: "Profondità" item: "Elemento" @@ -632,64 +632,68 @@ it: no_results_title_text: Al momento non vi sono versioni disponibili. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Aggiungi relazioni ad altre macro-attività per creare un collegamento tra di esse." + no_results_title_text: Al momento non vi sono relazioni disponibili. + blankslate_heading: "Nessuna relazione" + blankslate_description: "Questa macro-attività non ha ancora alcuna relazione." + label_add_x: "Aggiungi %{x}" + label_edit_x: "Modifica %{x}" + label_add_description: "Aggiungi descrizione" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + label_relates_singular: "correlata con" + label_relates_plural: "correlate con" + label_relates_to_singular: "correlata con" + label_relates_to_plural: "correlate con" + relates_description: "Crea un collegamento visibile tra i due macro-attività senza alcun effetto aggiuntivo" + relates_to_description: "Crea un collegamento visibile tra i due macro-attività senza alcun effetto aggiuntivo" + label_precedes_singular: "successore (dopo)" + label_precedes_plural: "successori (dopo)" + precedes_description: "La macro-attività correlata deve necessariamente iniziare dopo la conclusione di questa" + label_follows_singular: "predecessore (prima)" + label_follows_plural: "predecessori (prima)" + follows_description: "La macro-attività correlata deve necessariamente terminare prima dell'inizio di questa" + label_child_singular: "figlia" + label_child_plural: "figlie" + child_description: "Rende la macro-attività correlata un sottoelemento della macro-attività corrente (genitore)" + label_blocks_singular: "blocca" + label_blocks_plural: "blocca" + blocks_description: "La relativa macro-attività non può essere chiusa fino a quando questa non sarà chiusa prima" + label_blocked_singular: "bloccata da" + label_blocked_plural: "bloccate da" + label_blocked_by_singular: "bloccata da" + label_blocked__by_plural: "bloccate da" + blocked_description: "Questa macro-attività non può essere chiusa fino a quando quella correlata non sarà chiusa prima" + blocked_by_description: "Questa macro-attività non può essere chiusa fino a quando quella correlata non sarà chiusa prima" + label_duplicates_singular: "duplica" + label_duplicates_plural: "duplica" + duplicates_description: "Questa è una copia della macro-attività correlata" + label_duplicated_singular: "duplicata da" + label_duplicated_plural: "duplicate da" + label_duplicated_by_singular: "duplicata da" + label_duplicated_by_plural: "duplicate da" + duplicated_by_description: "La macro-attività correlata è una copia di questa" + duplicated_description: "La macro-attività correlata è una copia di questa" + label_includes_singular: "include" + label_includes_plural: "include" + includes_description: "Contrassegna la macro-attività correlata come se includesse questa senza alcun effetto aggiuntivo" + label_partof_singular: "parte di" + label_partof_plural: "parte di" + label_part_of_singular: "parte di" + label_part_of_plural: "parte di" + partof_description: "Contrassegna la macro-attività correlata come se facesse parte di questa senza alcun effetto aggiuntivo" + part_of_description: "Contrassegna la macro-attività correlata come se facesse parte di questa senza alcun effetto aggiuntivo" + label_requires_singular: "richiede" + label_requires_plural: "richiede" + requires_description: "Contrassegna la macro-attività correlata come obbligatoria per questa" + label_required_singular: "richiesta da" + label_required_plural: "richieste da" + required_description: "Contrassegna questa macro-attività come obbligatoria per quella correlata" + label_parent_singular: "genitore" + label_parent_plural: "genitore" label_invitation: Invito account: delete: "Cancella account" @@ -1089,17 +1093,17 @@ it: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "deve essere Project::StageDefinition o Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "deve essere Project::Stage o Project::Gate" + must_be_a_stage: "deve essere un Project::Stage" + must_be_a_gate: "deve essere un Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "Impossibile assegnare `end_date` a un Project::Gate" query: attributes: project: @@ -1896,22 +1900,22 @@ it: hours: o days: g pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Pagina %{page} di %{total}" dialog: - title: Generate PDF - submit: Generate + title: Genera PDF + submit: Genera header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: Intestazione a destra + caption: Testo da visualizzare a destra dell'intestazione footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Piè di pagina centrale + caption: Testo da visualizzare al centro del piè di pagina hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Sillabazione + caption: Suddividi le parole tra le righe per migliorare la giustificazione e la leggibilità del testo. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Dimensione del foglio + caption: Il formato della carta da utilizzare per il PDF. extraction: available: pdftotext: "Pdfatesto disponibile (opzionale)" @@ -2270,7 +2274,7 @@ it: label_environment: "Ambiente" label_estimates_and_progress: "Stime e progressi" label_equals: "è" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "è qualsiasi con discendenti" label_everywhere: "ovunque" label_example: "Esempio" label_experimental: "Sperimentale" @@ -2535,8 +2539,8 @@ it: label_related_work_packages: "Macro-attività correlate" label_relates: "correlato con" label_relates_to: "correlato con" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "Relazione" + label_relation_actions: "Azioni di relazione" label_relation_delete: "Eliminare la relazione" label_relation_new: "Nuova relazione" label_release_notes: "Note di rilascio" diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 60c64a0f3ea8..562facbfc617 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -631,6 +631,10 @@ ja: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index c407e2777724..bf27e3438309 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -359,7 +359,7 @@ de: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ Die Version bringt verschiedene Funktionen und Verbesserungen für Sie, z.B.
  • Benutzerdefinierte Felder vom Typ Hierarchie (Enterprise Add-on).
  • Neugestaltung des Beziehungen-Tabs in Arbeitspaketen.
  • Neugestaltung der Meeting-Indexseite.
  • Manuelle Seitenumbrüche beim Export von PDF-Arbeitspaketen.
  • Zen-Modus für Projektlisten.
ical_sharing_modal: title: "Kalender abonnieren" inital_setup_error_message: "Beim Abrufen der Daten ist ein Fehler aufgetreten." diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml index b2fd8f5bb9ca..ec6b4dc0a3e7 100644 --- a/config/locales/crowdin/js-es.yml +++ b/config/locales/crowdin/js-es.yml @@ -105,7 +105,7 @@ es: button_update: "Actualizar" button_export-pdf: "Descargar PDF" button_export-atom: "Descargar Atom" - button_generate_pdf: "Generate PDF" + button_generate_pdf: "Generar PDF" button_create: "Crear" card: add_new: "Añadir nueva tarjeta" @@ -360,7 +360,7 @@ es: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ La versión trae varias características y mejoras para usted, por ejemplo
  • Campos personalizados de tipo jerarquía (extensión Enterprise).
  • Rediseño de la pestaña Relaciones en los paquetes de trabajo.
  • Rediseño de la página de índice de Reuniones.
  • Saltos de página manuales en las exportaciones de paquetes de trabajo en PDF.
  • Modo Zen para las listas de proyectos.
ical_sharing_modal: title: "Suscribirse al calendario" inital_setup_error_message: "Se ha producido un error al obtener los datos." @@ -576,7 +576,7 @@ es: members: "Invitar a nuevos miembros a unirse a tu proyecto." quick_add_button: "Haga clic en el icono del signo más (+) en el menú de navegación del encabezado para crear un proyecto o invitar a compañeros." sidebar_arrow: "Usa la flecha de retorno en la esquina superior izquierda para regresar al menú principal del proyecto." - welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." + welcome: "Realice un recorrido de introducción de tres minutos para conocer las funciones más importantes.
Le recomendamos que complete todos los pasos. Puede volver a iniciar el paseo en cualquier momento." wiki: "En la wiki, puede elaborar documentos y compartir conocimientos con su equipo." backlogs: overview: "Administre su trabajo en la vista de trabajos pendientes." diff --git a/config/locales/crowdin/js-fr.yml b/config/locales/crowdin/js-fr.yml index 7b00b593544a..e0e34fe8c835 100644 --- a/config/locales/crowdin/js-fr.yml +++ b/config/locales/crowdin/js-fr.yml @@ -360,7 +360,7 @@ fr: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ Cette version apporte diverses fonctionnalités et améliorations, par exemple
  • Champs personnalisés de type hiérarchique (module Enterprise).
  • Refonte de l'onglet Relations dans les lots de travaux.
  • Refonte de la page d'index des réunions.
  • Sauts de page manuels dans les exportations de lots de travaux PDF.
  • Mode Zen pour les listes de projets.
ical_sharing_modal: title: "S'abonner au calendrier" inital_setup_error_message: "Une erreur est survenue lors de la récupération des données." diff --git a/config/locales/crowdin/js-it.yml b/config/locales/crowdin/js-it.yml index 41e9de7e47b9..09c339e1e3c4 100644 --- a/config/locales/crowdin/js-it.yml +++ b/config/locales/crowdin/js-it.yml @@ -105,7 +105,7 @@ it: button_update: "Aggiorna" button_export-pdf: "Scarica PDF" button_export-atom: "Scarica Atom" - button_generate_pdf: "Generate PDF" + button_generate_pdf: "Genera PDF" button_create: "Crea" card: add_new: "Aggiungi una nuova carta" @@ -360,7 +360,7 @@ it: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ Questa versione offre varie funzionalità e miglioramenti, ad esempio
  • Campi personalizzati di tipo gerarchia (componente aggiuntivo Enterprise).
  • Riprogettazione della scheda Relazioni nelle macro-attività.
  • Riprogettazione della pagina indice Riunioni.
  • Interruzioni di pagina manuali nelle esportazioni dei PDF delle macro-attività.
  • Modalità Zen per gli elenchi di progetti.
ical_sharing_modal: title: "Iscriviti al calendario" inital_setup_error_message: "Si è verificato un errore recuperando i dati." @@ -576,7 +576,7 @@ it: members: "Invita nuovi membri per entrare al tuo progetto." quick_add_button: "Clicca sull'icona più (+) nella navigazione dell'intestazione per creare un nuovo progetto o per invitare colleghi." sidebar_arrow: "Utilizza la freccia di ritorno nell'angolo in alto a sinistra per tornare al menu principale del progetto." - welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." + welcome: "Fai un tour introduttivo di tre minuti per apprendere le funzioni importanti.
Consigliamo di completare i passaggi fino alla fine. Puoi riavviare il tour in qualsiasi momento." wiki: "Nella wiki puoi documentare e condividere la conoscenza con il tuo team." backlogs: overview: "Gestisci il tuo lavoro nella vista backlog." diff --git a/config/locales/crowdin/js-pl.yml b/config/locales/crowdin/js-pl.yml index 15a751e25827..2b0d733107b2 100644 --- a/config/locales/crowdin/js-pl.yml +++ b/config/locales/crowdin/js-pl.yml @@ -105,7 +105,7 @@ pl: button_update: "Aktualizacja" button_export-pdf: "Pobierz plik PDF" button_export-atom: "Pobierz Atom" - button_generate_pdf: "Generate PDF" + button_generate_pdf: "Wygeneruj PDF" button_create: "Utwórz" card: add_new: "Dodaj nową kartę" @@ -360,7 +360,7 @@ pl: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ Ta wersja wprowadza różne funkcje i ulepszenia, np.
  • Niestandardowe pola hierarchii typów (dodatek wersji Enterprise).
  • Przeprojektowanie karty Relacje w pakietach roboczych.
  • Przeprojektowanie strony indeksu Spotkania.
  • Ręczne podziały stron w eksporcie pakietów roboczych w formacie PDF.
  • Tryb Zen dla list projektów.
ical_sharing_modal: title: "Subskrybuj kalendarz" inital_setup_error_message: "Podczas pobierania danych wystąpił błąd." @@ -576,7 +576,7 @@ pl: members: "Zaproś nowych członków do swojego projektu." quick_add_button: "Kliknij ikonę plus (+) w nawigacji nagłówka, aby utworzyć nowy projekt lub zaprosić współpracowników." sidebar_arrow: "Użyj strzałki powrotu w lewym górnym rogu, aby wrócić do menu głównego projektu." - welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." + welcome: "Poświęć trzy minuty na zapoznanie się z najważniejszymi funkcjami. \n
Zalecamy wykonanie tych kroków do końca. Zwiedzanie można w każdej chwili zacząć od nowa." wiki: "W wiki możesz dokumentować wiedzę i dzielić się nią z zespołem." backlogs: overview: "Zarządzaj swoją pracą w widoku backlogs." diff --git a/config/locales/crowdin/js-pt-BR.yml b/config/locales/crowdin/js-pt-BR.yml index 0d354c51128e..f676f73da14d 100644 --- a/config/locales/crowdin/js-pt-BR.yml +++ b/config/locales/crowdin/js-pt-BR.yml @@ -105,7 +105,7 @@ pt-BR: button_update: "Atualizar" button_export-pdf: "Baixar PDF" button_export-atom: "Baixar Atom" - button_generate_pdf: "Generate PDF" + button_generate_pdf: "Gerar PDF" button_create: "Criar" card: add_new: "Adicionar novo cartão" @@ -359,7 +359,7 @@ pt-BR: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ A nova versão traz diversas melhorias e recursos, como:
  • Campos personalizados em hierarquia (complemento Enterprise).
  • Redesign da aba de Relações nos pacotes de trabalho.
  • Redesign da página de índice das Reuniões.
  • Quebras de página manuais nas exportações de pacotes de trabalho em PDF.
  • Modo Zen para listas de projetos.
ical_sharing_modal: title: "Assinar calendário" inital_setup_error_message: "Ocorreu um erro ao buscar dados." @@ -575,7 +575,7 @@ pt-BR: members: "Convide novos membros para participar de seu projeto." quick_add_button: "Clique no ícone mais (+) no menu de navegação para criar um novo projeto ou para convidar colegas de trabalho." sidebar_arrow: "Use a seta de retorno no canto superior esquerdo para retornar ao menu principal do projeto." - welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." + welcome: "Faça um tour de introdução de três minutos para conhecer os principais recursos.
Recomendamos que você siga todas as etapas até o final. O tour pode ser reiniciado a qualquer momento." wiki: "Na wiki, você pode documentar e compartilhar conhecimento junto com sua equipe. " backlogs: overview: "Gerencie seu trabalho na visão de backlogs." diff --git a/config/locales/crowdin/js-pt-PT.yml b/config/locales/crowdin/js-pt-PT.yml index b6804f138edf..399ebcc12486 100644 --- a/config/locales/crowdin/js-pt-PT.yml +++ b/config/locales/crowdin/js-pt-PT.yml @@ -105,7 +105,7 @@ pt-PT: button_update: "Atualizar" button_export-pdf: "Transferir PDF" button_export-atom: "Transferir Atom" - button_generate_pdf: "Generate PDF" + button_generate_pdf: "Gerar PDF" button_create: "Criar" card: add_new: "Adicionar novo cartão" @@ -360,7 +360,7 @@ pt-PT: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ A versão oferece várias funcionalidades e melhorias, por exemplo,
  • Campos personalizados de tipo hierárquico (suplemento Enterprise).
  • Reformulação do separador Relações nos pacotes de trabalho.
  • Reformulação da página de índice das Reuniões.
  • Quebras de página manuais nas exportações de pacotes de trabalho em PDF.
  • Modo Zen para listas de projetos.
ical_sharing_modal: title: "Subscrever o calendário" inital_setup_error_message: "Ocorreu um erro ao recuperar os dados." @@ -576,7 +576,7 @@ pt-PT: members: "Convide novos membros para participarem no seu projeto." quick_add_button: "Clique no ícone mais (+) no menu de navegação para criar um novo projeto ou convidar colegas." sidebar_arrow: "Use a seta de retorno no canto superior esquerdo para voltar ao menu principal do projeto." - welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." + welcome: "Faça uma visita guiada de 3 minutos para conhecer as funcionalidades mais importantes.
Recomendamos que conclua as etapas até ao final. Pode reiniciar a visita a qualquer momento." wiki: "Na wiki pode documentar e partilhar conhecimento com a sua equipa." backlogs: overview: "Faça a gestão do seu trabalho na vista de backlogs." diff --git a/config/locales/crowdin/js-zh-CN.yml b/config/locales/crowdin/js-zh-CN.yml index 0e82904e0fef..d1c4b79d83af 100644 --- a/config/locales/crowdin/js-zh-CN.yml +++ b/config/locales/crowdin/js-zh-CN.yml @@ -359,7 +359,7 @@ zh-CN: "15_1": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
  • Custom fields of type hierarchy (Enterprise add-on).
  • Redesign of the Relations tab in work packages.
  • Redesign of the Meetings index page.
  • Manual page breaks in PDF work package exports.
  • Zen mode for project lists.
+ 该版本为您带来了多种新功能和改进,例如
  • 分层类型的自定义字段(企业附加组件)。
  • 重新设计工作包中的关系选项卡。
  • 重新设计会议索引页。
  • 在导出 PDF 工作包时手动分页。
  • 项目列表的禅模式。
ical_sharing_modal: title: "订阅日历" inital_setup_error_message: "获取数据时发生错误。" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index e31b6704f3da..2b9b7c6c9408 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -643,6 +643,10 @@ ka: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 99d7085f6daa..8c64eb2cb491 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -643,6 +643,10 @@ kk: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 176131f118b0..8a9f4c26a307 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -634,6 +634,10 @@ ko: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 8002db4323ce..0aeca5107a7c 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -656,6 +656,10 @@ lt: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index b83d690da202..1f2af424fe18 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -651,6 +651,10 @@ lv: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 2b79c35c23f1..cf7c25b71c10 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -643,6 +643,10 @@ mn: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index e0357345ae75..c48249bdfdc4 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -633,6 +633,10 @@ ms: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index f7b27f78b405..93be92c6de27 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -643,6 +643,10 @@ ne: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 2978b0cbc53d..3dde1a7bd66c 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -640,6 +640,10 @@ nl: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 9349a3d50ae5..c96318442740 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -642,6 +642,10 @@ label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/pl.seeders.yml b/config/locales/crowdin/pl.seeders.yml index 604e3aa630ef..8f3f0966986a 100644 --- a/config/locales/crowdin/pl.seeders.yml +++ b/config/locales/crowdin/pl.seeders.yml @@ -36,15 +36,15 @@ pl: name: Czarny life_cycle_colors: item_0: - name: PM2 Orange + name: PM2 pomarańczowy item_1: - name: PM2 Purple + name: PM2 fioletowy item_2: - name: PM2 Red + name: PM2 czerwony item_3: - name: PM2 Magenta + name: PM2 amarantowy item_4: - name: PM2 Green Yellow + name: PM2 żółtozielony document_categories: item_0: name: Dokumentacja @@ -83,19 +83,19 @@ pl: standard: life_cycles: item_0: - name: Initiating + name: Inicjowanie item_1: - name: Ready for Planning + name: Gotowy do planowania item_2: - name: Planning + name: Planowanie item_3: - name: Ready for Executing + name: Gotowy do wykonania item_4: - name: Executing + name: Wykonywanie item_5: - name: Ready for Closing + name: Gotowy do zamknięcia item_6: - name: Closing + name: Zamykanie priorities: item_0: name: Niski diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 2dd618a52553..ff754974e434 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -33,7 +33,7 @@ pl: label_activity_show_only_changes: "Pokaż tylko zmiany" label_sort_asc: "Najnowsze na końcu" label_sort_desc: "Najnowsze na początku" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Dodaj komentarz. Wpisz @, aby powiadomić innych." label_submit_comment: "Prześlij komentarz" changed_on: "zmieniono" created_on: "utworzono to" @@ -237,7 +237,7 @@ pl: one: 1 podelement other: "Liczba podelementów: %{count}" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Potrzebujesz hierarchii w polach niestandardowych pakietów roboczych?" text_add_new_custom_field: > Aby dodać do projektu nowe pola niestandardowe, najpierw należy je utworzyć. is_enabled_globally: "Jest włączony globalnie" @@ -264,20 +264,20 @@ pl: single: "lub" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "musi być liczbą całkowitą." + filled?: "musi być wypełnione." + greater_or_equal_zero: "musi mieć wartość większą lub równą 0." + not_found: "nie znaleziono." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "nie może być elementem głównym." + not_persisted: "musi być już istniejącym elementem." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "muszą być unikalne na tym samym poziomie hierarchii." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "muszą być unikalne na tym samym poziomie hierarchii." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "musi być potomkiem katalogu głównego hierarchii." rules: depth: "Głębokość" item: "Element" @@ -649,64 +649,68 @@ pl: no_results_title_text: Obecnie żadne wersje nie są dostępne. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Dodaj relacje do innych pakietów roboczych, aby utworzyć powiązanie między nimi." + no_results_title_text: Obecnie nie są dostępne żadne relacje. + blankslate_heading: "Brak relacji" + blankslate_description: "Ten pakiet roboczy nie ma jeszcze żadnych relacji." + label_add_x: "Dodaj %{x}" + label_edit_x: "Edytuj %{x}" + label_add_description: "Dodaj opis" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + label_relates_singular: "ma relację z" + label_relates_plural: "ma relację z" + label_relates_to_singular: "ma relację z" + label_relates_to_plural: "ma relację z" + relates_description: "Tworzy widoczne powiązanie między dwoma pakietami roboczymi bez dodatkowego wpływu" + relates_to_description: "Tworzy widoczne powiązanie między dwoma pakietami roboczymi bez dodatkowego wpływu" + label_precedes_singular: "następca (po)" + label_precedes_plural: "następcy (po)" + precedes_description: "Powiązany pakiet roboczy musi koniecznie rozpocząć się po zakończeniu tego pakietu" + label_follows_singular: "poprzednik (przed)" + label_follows_plural: "poprzednicy (przed)" + follows_description: "Powiązany pakiet roboczy musi koniecznie zostać ukończony przed rozpoczęciem tego pakietu" + label_child_singular: "element podrzędny" + label_child_plural: "elementy podrzędne" + child_description: "Sprawia, że powiązany pakiet roboczy staje się elementem podrzędnym bieżącego (nadrzędnego) pakietu roboczego" + label_blocks_singular: "blok" + label_blocks_plural: "bloki" + blocks_description: "Powiązany pakiet roboczy nie może zostać zamknięty, dopóki najpierw nie zostanie zamknięty ten pakiet roboczy" + label_blocked_singular: "blokowany przez" + label_blocked_plural: "blokowany przez" + label_blocked_by_singular: "blokowany przez" + label_blocked__by_plural: "blokowany przez" + blocked_description: "Ten pakiet roboczy nie może zostać zamknięty, dopóki najpierw nie zostanie zamknięty powiązany pakiet roboczy" + blocked_by_description: "Ten pakiet roboczy nie może zostać zamknięty, dopóki najpierw nie zostanie zamknięty powiązany pakiet roboczy" + label_duplicates_singular: "duplikat" + label_duplicates_plural: "duplikaty" + duplicates_description: "To jest kopia powiązanego pakietu roboczego" + label_duplicated_singular: "zduplikowany przez" + label_duplicated_plural: "zduplikowane przez" + label_duplicated_by_singular: "zduplikowany przez" + label_duplicated_by_plural: "zduplikowane przez" + duplicated_by_description: "Powiązany pakiet roboczy jest kopią tego" + duplicated_description: "Powiązany pakiet roboczy jest kopią tego" + label_includes_singular: "zawiera" + label_includes_plural: "zawierają" + includes_description: "Oznacza powiązany pakiet roboczy jako zawierający ten pakiet roboczy bez dodatkowego wpływu" + label_partof_singular: "część" + label_partof_plural: "część" + label_part_of_singular: "część" + label_part_of_plural: "część" + partof_description: "Zaznacza powiązany pakiet roboczy jako część tego pakietu roboczego bez dodatkowego wpływu" + part_of_description: "Oznacza powiązany pakiet roboczy jako będący częścią tego pakietu roboczego bez dodatkowego wpływu" + label_requires_singular: "wymaga" + label_requires_plural: "wymagają" + requires_description: "Oznacza powiązany pakiet roboczy jako wymaganie dla tego pakietu roboczego" + label_required_singular: "wymagany przez" + label_required_plural: "wymagany przez" + required_description: "Oznacza ten pakiet roboczy jako wymaganie powiązanego pakietu roboczego" + label_parent_singular: "nadrzędny" + label_parent_plural: "nadrzędne" label_invitation: Zaproszenie account: delete: "Usuń konto" @@ -1106,17 +1110,17 @@ pl: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "musi mieć wartość Project::StageDefinition albo Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "musi mieć wartość Project::Stage albo Project::Gate" + must_be_a_stage: "musi mieć wartość Project::Stage" + must_be_a_gate: "musi mieć wartość Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "Nie można przypisać „end_date” do Project::Gate" query: attributes: project: @@ -1969,22 +1973,22 @@ pl: hours: h days: d pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Strona %{page} z %{total}" dialog: - title: Generate PDF - submit: Generate + title: Wygeneruj PDF + submit: Wygeneruj header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: Nagłówek z prawej + caption: Tekst wyświetlany po prawej stronie nagłówka footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Środek stopki + caption: Tekst, który ma być wyświetlany na środku stopki hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Dzielenie wyrazów + caption: Dzielenie wyrazów między wierszami w celu poprawy justowania i czytelności tekstu. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Rozmiar papieru + caption: Rozmiar papieru, który ma zostać użyty w pliku PDF. extraction: available: pdftotext: "Dostępny konwerter Pdftotext (opcjonalny)" @@ -2343,7 +2347,7 @@ pl: label_environment: "Środowisko" label_estimates_and_progress: "Szacunki i postęp" label_equals: "jest" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "jest dowolny z potomkami" label_everywhere: "wszędzie" label_example: "Przykład" label_experimental: "Eksperymentalny" @@ -2608,8 +2612,8 @@ pl: label_related_work_packages: "Powiązanie pakiety robocze" label_relates: "Powiązane z" label_relates_to: "Powiązane z" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "Relacja" + label_relation_actions: "Działania relacji" label_relation_delete: "Usuń powiązanie" label_relation_new: "Nowe powiązanie" label_release_notes: "Dziennik zamian" diff --git a/config/locales/crowdin/pt-BR.seeders.yml b/config/locales/crowdin/pt-BR.seeders.yml index 53dab48ca250..7feaa065f8b9 100644 --- a/config/locales/crowdin/pt-BR.seeders.yml +++ b/config/locales/crowdin/pt-BR.seeders.yml @@ -36,15 +36,15 @@ pt-BR: name: Preto life_cycle_colors: item_0: - name: PM2 Orange + name: PM2 Laranja item_1: - name: PM2 Purple + name: PM2 Roxo item_2: - name: PM2 Red + name: PM2 Vermelho item_3: name: PM2 Magenta item_4: - name: PM2 Green Yellow + name: PM2 Verde amarelo document_categories: item_0: name: Documentação @@ -83,19 +83,19 @@ pt-BR: standard: life_cycles: item_0: - name: Initiating + name: Iniciando item_1: - name: Ready for Planning + name: Pronto para o planejamento item_2: - name: Planning + name: Planejamento item_3: - name: Ready for Executing + name: Pronto para a execução item_4: - name: Executing + name: Execução item_5: - name: Ready for Closing + name: Pronto para o fechamento item_6: - name: Closing + name: Fechamento priorities: item_0: name: Baixa diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index af4d02fe073d..aae04a983acd 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -33,7 +33,7 @@ pt-BR: label_activity_show_only_changes: "Exibir apenas alterações" label_sort_asc: "Mais recentes embaixo" label_sort_desc: "Mais recentes em cima" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Adicione um comentário. Digite @ para notificar as pessoas." label_submit_comment: "Enviar comentário" changed_on: "alterado em" created_on: "criou em" @@ -239,7 +239,7 @@ pt-BR: one: 1 subitem other: "%{count} subitens" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Precisa de hierarquia nos campos personalizados dos pacotes de trabalho?" text_add_new_custom_field: > Para adicionar campos personalizados a um projeto é necessário criá-los primeiro para depois adicioná-los a este projeto. is_enabled_globally: "Está habilitado globalmente" @@ -633,64 +633,68 @@ pt-BR: no_results_title_text: Atualmente, não há versões disponíveis. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Adicione relações com outros pacotes de trabalho para criar um vínculo entre eles." + no_results_title_text: No momento, não há relações disponíveis. + blankslate_heading: "Sem vínculos" + blankslate_description: "Este pacote de trabalho ainda não está relacionado a nenhum outro." + label_add_x: "Adicionar %{x}" + label_edit_x: "Editar %{x}" + label_add_description: "Adicionar descrição" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + label_relates_singular: "relacionado a" + label_relates_plural: "relacionado a" + label_relates_to_singular: "relacionado a" + label_relates_to_plural: "relacionado a" + relates_description: "Cria um vínculo visível entre os dois pacotes de trabalho, sem efeitos adicionais" + relates_to_description: "Cria um vínculo visível entre os dois pacotes de trabalho, sem efeitos adicionais" + label_precedes_singular: "sucessor (depois)" + label_precedes_plural: "sucessores (depois)" + precedes_description: "O pacote de trabalho relacionado deve começar somente após a conclusão deste" + label_follows_singular: "anterior (antes)" + label_follows_plural: "anteriores (antes)" + follows_description: "O pacote de trabalho relacionado deve ser finalizado antes que este possa ser iniciado" + label_child_singular: "filho" + label_child_plural: "filhos" + child_description: "Torna o pacote de trabalho relacionado uma subtarefa do pacote de trabalho atual (pai)" + label_blocks_singular: "blocos" + label_blocks_plural: "blocos" + blocks_description: "O pacote de trabalho relacionado não pode ser concluído até que este seja concluído primeiro" + label_blocked_singular: "bloqueado por" + label_blocked_plural: "bloqueado por" + label_blocked_by_singular: "bloqueado por" + label_blocked__by_plural: "bloqueado por" + blocked_description: "Este pacote de trabalho não pode ser fechado até que o relacionado seja fechado primeiro" + blocked_by_description: "Este pacote de trabalho não pode ser fechado até que o relacionado seja fechado primeiro" + label_duplicates_singular: "duplicados" + label_duplicates_plural: "duplicados" + duplicates_description: "Esta é uma cópia do pacote de trabalho relacionado" + label_duplicated_singular: "duplicado por" + label_duplicated_plural: "duplicado por" + label_duplicated_by_singular: "duplicado por" + label_duplicated_by_plural: "duplicado por" + duplicated_by_description: "O pacote de trabalho relacionado é uma cópia deste" + duplicated_description: "O pacote de trabalho relacionado é uma cópia deste" + label_includes_singular: "inclui" + label_includes_plural: "inclui" + includes_description: "Marca o pacote de trabalho relacionado como incluindo este, sem efeito adicional" + label_partof_singular: "parte de" + label_partof_plural: "parte de" + label_part_of_singular: "parte de" + label_part_of_plural: "parte de" + partof_description: "Marca o pacote de trabalho relacionado como parte deste, sem efeito adicional" + part_of_description: "Marca o pacote de trabalho relacionado como parte deste, sem efeito adicional" + label_requires_singular: "exige" + label_requires_plural: "exige" + requires_description: "Marca o pacote de trabalho relacionado como um pré-requisito para este" + label_required_singular: "exigido por" + label_required_plural: "exigido por" + required_description: "Marca este pacote de trabalho como um requisito para o pacote de trabalho relacionado" + label_parent_singular: "pais" + label_parent_plural: "pais" label_invitation: Convite account: delete: "Excluir conta" @@ -1090,17 +1094,17 @@ pt-BR: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "deve ser Project::StageDefinition ou Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "deve ser Project::Stage ou Project::Gate" + must_be_a_stage: "deve ser um Project::Stage" + must_be_a_gate: "deve ser um Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "Não é possível atribuir end_date a um Project::Gate" query: attributes: project: @@ -1897,22 +1901,22 @@ pt-BR: hours: h days: d pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Página %{page} de %{total}" dialog: - title: Generate PDF - submit: Generate + title: Gerar PDF + submit: Gerar header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: Cabeçalho à direita + caption: Texto a ser exibido à direita do cabeçalho footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Centro do rodapé + caption: Texto a ser exibido no centro do rodapé hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Hifenização + caption: Quebra de palavras entre as linhas para melhorar a justificação e a legibilidade do texto. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Tamanho do papel + caption: O tamanho do papel a ser usado para o PDF. extraction: available: pdftotext: "Pdftotext disponível (opcional)" @@ -2271,7 +2275,7 @@ pt-BR: label_environment: "Ambiente" label_estimates_and_progress: "Estimativas e progresso" label_equals: "é" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "é qualquer um que tenha descendentes" label_everywhere: "em todos os lugares" label_example: "Exemplo" label_experimental: "Experimental" @@ -2536,8 +2540,8 @@ pt-BR: label_related_work_packages: "Pacotes de trabalho relacionados" label_relates: "relacionado a" label_relates_to: "relacionado a" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "Relação" + label_relation_actions: "Ações da relação" label_relation_delete: "Excluir relação" label_relation_new: "Nova relação" label_release_notes: "Notas da versão" diff --git a/config/locales/crowdin/pt-PT.seeders.yml b/config/locales/crowdin/pt-PT.seeders.yml index 9763deec66c7..29fef74b0c1a 100644 --- a/config/locales/crowdin/pt-PT.seeders.yml +++ b/config/locales/crowdin/pt-PT.seeders.yml @@ -36,15 +36,15 @@ pt-PT: name: Preto life_cycle_colors: item_0: - name: PM2 Orange + name: PM2 Laranja item_1: - name: PM2 Purple + name: PM2 Roxo item_2: - name: PM2 Red + name: PM2 Vermelho item_3: name: PM2 Magenta item_4: - name: PM2 Green Yellow + name: PM2 Verde amarelo document_categories: item_0: name: Documentação @@ -83,19 +83,19 @@ pt-PT: standard: life_cycles: item_0: - name: Initiating + name: A iniciar item_1: - name: Ready for Planning + name: Pronto para o planeamento item_2: - name: Planning + name: Planeamento item_3: - name: Ready for Executing + name: Pronto para a execução item_4: - name: Executing + name: Execução item_5: - name: Ready for Closing + name: Pronto para o encerramento item_6: - name: Closing + name: Encerramento priorities: item_0: name: Baixo diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 446b9a79fc98..009e579bd00d 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -33,7 +33,7 @@ pt-PT: label_activity_show_only_changes: "Mostrar apenas alterações" label_sort_asc: "Mais recente em baixo" label_sort_desc: "Mais recente no topo" - label_type_to_comment: "Add a comment. Type @ to notify people." + label_type_to_comment: "Adicione um comentário. Escreva @ para notificar as pessoas." label_submit_comment: "Submeter comentário" changed_on: "alterou a" created_on: "criou isto a" @@ -238,7 +238,7 @@ pt-PT: one: 1 sub-item other: "%{count} sub-items" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Precisa de uma hierarquia nos seus campos personalizados para pacotes de trabalho?" text_add_new_custom_field: > Para adicionar novos campos personalizados a um projeto, primeiro precisa de criá-los para depois adicioná-los a este projeto. is_enabled_globally: "Ativado a nível global" @@ -265,20 +265,20 @@ pt-PT: single: "ou" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "tem de ser um número inteiro." + filled?: "tem de estar preenchido." + greater_or_equal_zero: "tem de ser maior ou igual a 0." + not_found: "não encontrado." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "não pode ser um item de raiz." + not_persisted: "tem de ser um item já existente." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "tem de ser único dentro do mesmo nível hierárquico." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "tem de ser único dentro do mesmo nível hierárquico." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "tem de ser um descendente da raiz da hierarquia." rules: depth: "Profundidade" item: "Item" @@ -633,64 +633,68 @@ pt-PT: no_results_title_text: Atualmente, não existem versões disponíveis. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Adicione relações a outros pacotes de trabalho para criar uma ligação entre eles." + no_results_title_text: Atualmente, não existem relações disponíveis. + blankslate_heading: "Sem relações" + blankslate_description: "Este pacote de trabalho ainda não tem quaisquer relações." + label_add_x: "Adicionar %{x}" + label_edit_x: "Editar %{x}" + label_add_description: "Adicionar descrição" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" - label_blocked_singular: "blocked by" - label_blocked_plural: "blocked by" - label_blocked_by_singular: "blocked by" - label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" - label_duplicates_singular: "duplicates" - label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "duplicated by" - label_duplicated_plural: "duplicated by" - label_duplicated_by_singular: "duplicated by" - label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" - label_requires_singular: "requires" - label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" - label_required_singular: "required by" - label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + label_relates_singular: "relacionado com" + label_relates_plural: "relacionado com" + label_relates_to_singular: "relacionado com" + label_relates_to_plural: "relacionado com" + relates_description: "Cria uma ligação visível entre os dois pacotes de trabalho sem qualquer efeito adicional" + relates_to_description: "Cria uma ligação visível entre os dois pacotes de trabalho sem qualquer efeito adicional" + label_precedes_singular: "sucessor (depois)" + label_precedes_plural: "sucessores (depois)" + precedes_description: "O pacote de trabalho relacionado tem necessariamente de ser iniciado depois deste terminar" + label_follows_singular: "antecessor (antes)" + label_follows_plural: "antecessores (antes)" + follows_description: "O pacote de trabalho relacionado tem necessariamente de ser iniciado antes deste começar" + label_child_singular: "secundário" + label_child_plural: "secundários" + child_description: "Torna o pacote de trabalho relacionado num subitem do pacote de trabalho atual (principal)" + label_blocks_singular: "bloqueia" + label_blocks_plural: "bloqueia" + blocks_description: "O pacote de trabalho relacionado não pode ser encerrado até que este seja encerrado primeiro" + label_blocked_singular: "bloqueado por" + label_blocked_plural: "bloqueado por" + label_blocked_by_singular: "bloqueado por" + label_blocked__by_plural: "bloqueado por" + blocked_description: "Este pacote de trabalho não pode ser encerrado até que o pacote relacionado seja encerrado primeiro" + blocked_by_description: "Este pacote de trabalho não pode ser encerrado até que o pacote relacionado seja encerrado primeiro" + label_duplicates_singular: "duplicados" + label_duplicates_plural: "duplicados" + duplicates_description: "Esta é uma cópia do pacote de trabalho relacionado" + label_duplicated_singular: "duplicado por" + label_duplicated_plural: "duplicado por" + label_duplicated_by_singular: "duplicado por" + label_duplicated_by_plural: "duplicado por" + duplicated_by_description: "O pacote de trabalho relacionado é uma cópia deste" + duplicated_description: "O pacote de trabalho relacionado é uma cópia deste" + label_includes_singular: "inclui" + label_includes_plural: "inclui" + includes_description: "Marca o pacote de trabalho relacionado como incluindo este sem qualquer efeito adicional" + label_partof_singular: "parte de" + label_partof_plural: "parte de" + label_part_of_singular: "parte de" + label_part_of_plural: "parte de" + partof_description: "Marca o pacote de trabalho relacionado como sendo parte deste sem qualquer efeito adicional" + part_of_description: "Marca o pacote de trabalho relacionado como sendo parte deste sem qualquer efeito adicional" + label_requires_singular: "necessita" + label_requires_plural: "necessita" + requires_description: "Marca o pacote de trabalho relacionado como um requisito para este" + label_required_singular: "solicitado por" + label_required_plural: "solicitado por" + required_description: "Assinala este pacote de trabalho como sendo um requisito para o pacote relacionado" + label_parent_singular: "principal" + label_parent_plural: "principal" label_invitation: Convite account: delete: "Eliminar conta" @@ -1090,17 +1094,17 @@ pt-PT: project/life_cycle_step_definition: attributes: type: - must_be_a_stage_or_gate: "must be either Project::StageDefinition or Project::GateDefinition" + must_be_a_stage_or_gate: "tem de ser Project::StageDefinition ou Project::GateDefinition" project/life_cycle_step: attributes: type: - must_be_a_stage_or_gate: "must be either Project::Stage or Project::Gate" - must_be_a_stage: "must be a Project::Stage" - must_be_a_gate: "must be a Project::Gate" + must_be_a_stage_or_gate: "tem de ser Project::Stage ou Project::Gate" + must_be_a_stage: "tem de ser um Project::Stage" + must_be_a_gate: "tem de ser um Project::Gate" project/gate: attributes: base: - end_date_not_allowed: "Cannot assign `end_date` to a Project::Gate" + end_date_not_allowed: "Não é possível atribuir `end_date` a um Project::Gate" query: attributes: project: @@ -1897,22 +1901,22 @@ pt-PT: hours: h days: d pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Página %{page} de %{total}" dialog: - title: Generate PDF - submit: Generate + title: Gerar PDF + submit: Gerar header_right: - label: Header right - caption: Text to be displayed in the right of the header + label: Cabeçalho direito + caption: Texto a apresentar à direita do cabeçalho footer_center: - label: Footer center - caption: Text to be displayed in the center of the footer + label: Centro do rodapé + caption: Texto a apresentar no centro do rodapé hyphenation: - label: Hyphenation - caption: Break words between lines to improve text justification and readability. + label: Hifenização + caption: Separe as palavras entre linhas para melhorar a justificação e a legibilidade do texto. paper_size: - label: Paper size - caption: The size of the paper to use for the PDF. + label: Tamanho do papel + caption: O tamanho do papel a utilizar para o PDF. extraction: available: pdftotext: "Pdftotext disponível (opcional)" @@ -2271,7 +2275,7 @@ pt-PT: label_environment: "Ambiente" label_estimates_and_progress: "Estimativas e progresso" label_equals: "é" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "é qualquer um com descendentes" label_everywhere: "em todo o lado" label_example: "Exemplo" label_experimental: "Experimental" @@ -2536,8 +2540,8 @@ pt-PT: label_related_work_packages: "Tarefas relacionados" label_relates: "relacionado com" label_relates_to: "relacionado com" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "Relação" + label_relation_actions: "Ações da relação" label_relation_delete: "Eliminar relação" label_relation_new: "Nova relação" label_release_notes: "Notas de lançamento" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index a5e1f0222df1..2885f718f728 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -651,6 +651,10 @@ ro: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 951a6cb66fd0..af32df0277bc 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -658,6 +658,10 @@ ru: label_add_x: "Добавить %{x}" label_edit_x: "Редактировать %{x}" label_add_description: "Добавить описание" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "связан с" label_relates_plural: "связаны с" diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 39a514280586..6e621496b32d 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -643,6 +643,10 @@ rw: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 9643801aac70..5914fd6f4bb8 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -643,6 +643,10 @@ si: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index a7542273e4bd..c700385513e3 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -659,6 +659,10 @@ sk: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 66684c2325d5..a47ad182b7f7 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -656,6 +656,10 @@ sl: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index c3703e2c81a9..2449f8526159 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -651,6 +651,10 @@ sr: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 0ba760f392ce..dec05f5dd4df 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -642,6 +642,10 @@ sv: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 991edfc016ee..1fe11e919c76 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -635,6 +635,10 @@ th: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index d677aa8255fd..394f2d587513 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -641,6 +641,10 @@ tr: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/uk.seeders.yml b/config/locales/crowdin/uk.seeders.yml index cf35404eb4d4..eba58ecc9ea1 100644 --- a/config/locales/crowdin/uk.seeders.yml +++ b/config/locales/crowdin/uk.seeders.yml @@ -38,13 +38,13 @@ uk: item_0: name: PM2 Orange item_1: - name: PM2 Purple + name: Фіолетовий PM2 item_2: - name: PM2 Red + name: Червоний PM2 item_3: - name: PM2 Magenta + name: Пурпуровий PM2 item_4: - name: PM2 Green Yellow + name: Зелено-жовтий PM2 document_categories: item_0: name: Документація @@ -83,19 +83,19 @@ uk: standard: life_cycles: item_0: - name: Initiating + name: Ініціювання item_1: - name: Ready for Planning + name: Готовий до планування item_2: - name: Planning + name: Планування item_3: - name: Ready for Executing + name: Готовий до виконання item_4: - name: Executing + name: Виконання item_5: - name: Ready for Closing + name: Готовий до закриття item_6: - name: Closing + name: Закриття priorities: item_0: name: Низький diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 9e32422cd338..6704f302e1cf 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -235,7 +235,7 @@ uk: one: 1 піделемент other: "Піделементів: %{count}" upsale: - custom_field_format_hierarchy: "Need a hierarchy in your custom fields for work packages?" + custom_field_format_hierarchy: "Потрібно створити ієрархію в користувацьких полях для пакетів робіт?" text_add_new_custom_field: > Щоб додати нові користувальницькі поля до проекту, спочатку потрібно створити їх, перш ніж ви зможете додати їх до цього проекту. is_enabled_globally: "Увімкнено у всьому світі" @@ -262,20 +262,20 @@ uk: single: "або" dry_validation: errors: - int?: "must be an integer." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - not_found: "not found." + int?: "має бути цілим числом." + filled?: "повинно бути заповнено." + greater_or_equal_zero: "має дорівнювати нулю або перевищувати його." + not_found: "не знайдено." rules: item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "не може бути кореневим елементом." + not_persisted: "має бути вже наявним елементом." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "має бути унікальним у межах одного рівня ієрархії." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "має бути унікальним у межах одного рівня ієрархії." parent: - not_descendant: "must be a descendant of the hierarchy root." + not_descendant: "має бути дочірнім елементом кореня ієрархії." rules: depth: "Глибина" item: "Елемент" @@ -645,64 +645,68 @@ uk: no_results_title_text: Наразі немає доступних версій. work_package_relations_tab: index: - action_bar_title: "Add relations to other work packages to create a link between them." - no_results_title_text: There are currently no relations available. - blankslate_heading: "No relations" - blankslate_description: "This work package does not have any relations yet." - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Додайте зв’язки з іншими пакетами робіт." + no_results_title_text: Зараз немає доступних зв’язків. + blankslate_heading: "Немає зв’язків" + blankslate_description: "Цей пакет робіт ще не має зв’язків." + label_add_x: "Додати %{x}" + label_edit_x: "Редагувати %{x}" + label_add_description: "Додати опис" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: - label_relates_singular: "related to" - label_relates_plural: "related to" - label_relates_to_singular: "related to" - label_relates_to_plural: "related to" - relates_description: "Creates a visible link between the two work packages with no additional effect" - relates_to_description: "Creates a visible link between the two work packages with no additional effect" - label_precedes_singular: "successor (after)" - label_precedes_plural: "successors (after)" - precedes_description: "The related work package necessarily needs to start after this one finishes" - label_follows_singular: "predecessor (before)" - label_follows_plural: "predecessors (before)" - follows_description: "The related work package necessarily needs to finish before this one can start" - label_child_singular: "child" - label_child_plural: "children" - child_description: "Makes the related a work package a sub-item of the current (parent) work package" - label_blocks_singular: "blocks" - label_blocks_plural: "blocks" - blocks_description: "The related work package cannot be closed until this one is closed first" + label_relates_singular: "зв’язано з" + label_relates_plural: "зв’язано з" + label_relates_to_singular: "зв’язано з" + label_relates_to_plural: "зв’язано з" + relates_description: "Створює видимий зв’язок між двома пакетами робіт без додаткових ефектів" + relates_to_description: "Створює видимий зв’язок між двома пакетами робіт без додаткових ефектів" + label_precedes_singular: "наступник (після)" + label_precedes_plural: "наступники (після)" + precedes_description: "Зв’язаний пакет робіт обов’язково має починатися після завершення цього" + label_follows_singular: "попередник (до)" + label_follows_plural: "попередники (до)" + follows_description: "Зв’язаний пакет робіт обов’язково має завершуватися перед початком цього" + label_child_singular: "дочірній елемент" + label_child_plural: "дочірні елементи" + child_description: "Вкладає звʼязаний пакет робіт як піделемент поточного (батьківського) пакета робіт" + label_blocks_singular: "блоки" + label_blocks_plural: "блоки" + blocks_description: "Звʼязаний пакет робіт можна закрити лише після закриття цього" label_blocked_singular: "blocked by" label_blocked_plural: "blocked by" label_blocked_by_singular: "blocked by" label_blocked__by_plural: "blocked by" - blocked_description: "This work package cannot be closed until the related one is closed first" - blocked_by_description: "This work package cannot be closed until the related one is closed first" + blocked_description: "Цей пакет робіт можна закрити лише після закриття звʼязаного" + blocked_by_description: "Цей пакет робіт можна закрити лише після закриття звʼязаного" label_duplicates_singular: "duplicates" label_duplicates_plural: "duplicates" - duplicates_description: "This is a copy of the related work package" + duplicates_description: "Це копія звʼязаного пакета робіт" label_duplicated_singular: "duplicated by" label_duplicated_plural: "duplicated by" label_duplicated_by_singular: "duplicated by" label_duplicated_by_plural: "duplicated by" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" - label_includes_singular: "includes" - label_includes_plural: "includes" - includes_description: "Marks the related work package as including this one with no additional effect" - label_partof_singular: "part of" - label_partof_plural: "part of" - label_part_of_singular: "part of" - label_part_of_plural: "part of" - partof_description: "Marks the related work package as being part of this one with no additional effect" - part_of_description: "Marks the related work package as being part of this one with no additional effect" + duplicated_by_description: "Звʼязаний пакет робіт є копією цього" + duplicated_description: "Звʼязаний пакет робіт є копією цього" + label_includes_singular: "включає" + label_includes_plural: "включає" + includes_description: "Позначає звʼязаний пакет робіт як такий, що включає цей, без додаткового ефекту" + label_partof_singular: "належить до" + label_partof_plural: "належить до" + label_part_of_singular: "належить до" + label_part_of_plural: "належить до" + partof_description: "Позначає звʼязаний пакет робіт як такий, що належить до цього, без додаткового ефекту" + part_of_description: "Позначає звʼязаний пакет робіт як такий, що належить до цього, без додаткового ефекту" label_requires_singular: "requires" label_requires_plural: "requires" - requires_description: "Marks the related work package as a requirement to this one" + requires_description: "Позначає звʼязаний пакет робіт як обовʼязковий для цього" label_required_singular: "required by" label_required_plural: "required by" - required_description: "Marks this work package as being a requirement to the related one" - label_parent_singular: "parent" - label_parent_plural: "parent" + required_description: "Позначає цей робочий пакет як обовʼязковий для звʼязаного з ним" + label_parent_singular: "батьківський елемент" + label_parent_plural: "батьківський елемент" label_invitation: Запрошення account: delete: "Видалити обліковий запис" diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index 57b15c854d3b..9196833f599a 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -643,6 +643,10 @@ uz: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 54de24adfb31..2d4d7fe1097d 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -637,6 +637,10 @@ vi: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/locales/crowdin/zh-CN.seeders.yml b/config/locales/crowdin/zh-CN.seeders.yml index 94f22a7b9d39..ddc400fa4b41 100644 --- a/config/locales/crowdin/zh-CN.seeders.yml +++ b/config/locales/crowdin/zh-CN.seeders.yml @@ -91,11 +91,11 @@ zh-CN: item_3: name: 准备执行 item_4: - name: 正在执行 + name: 执行中 item_5: name: 准备关闭 item_6: - name: 正在关闭 + name: 关闭中 priorities: item_0: name: 低 diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 4224afbc7d15..1b782fed0c63 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -630,6 +630,10 @@ zh-CN: label_add_x: "添加 %{x}" label_edit_x: "编辑 %{x}" label_add_description: "添加说明" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "关联到" label_relates_plural: "关联到" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index e4050a26a0c6..624add7b9de5 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -632,6 +632,10 @@ zh-TW: label_add_x: "新增 %{x}" label_edit_x: "編輯:%{x}" label_add_description: "新增說明" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "相關於" label_relates_plural: "相關於" diff --git a/config/locales/en.yml b/config/locales/en.yml index 33f066d35cf5..8e7e65ae2d1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -539,8 +539,12 @@ en: groups: member_in_these_groups: "This user is currently a member of the following groups:" no_results_title_text: This user is currently not a member in any group. + summary_with_more: Member of %{names} and %{count_link}. + more: "%{count} more" + summary: Member of %{names}. memberships: no_results_title_text: This user is currently not a member of a project. + open_profile: "Open profile" page: text: "Text" placeholder_users: @@ -716,6 +720,10 @@ en: label_add_x: "Add %{x}" label_edit_x: "Edit %{x}" label_add_description: "Add description" + lag: + subject: "Lag" + title: "Lag (in days)" + caption: "The gap in number of working days in between the two work packages" relations: label_relates_singular: "related to" label_relates_plural: "related to" diff --git a/config/routes.rb b/config/routes.rb index 0c0c8d70ae21..aedb24d41b19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -648,6 +648,7 @@ resources :memberships, controller: "users/memberships", only: %i[update create destroy] member do + get "/hover_card" => "users/hover_card#show" get "/edit(/:tab)" => "users#edit", as: "edit" get "/change_status/:change_action" => "users#change_status_info", as: "change_status_info" post :change_status diff --git a/docker-compose.override.example.yml b/docker-compose.override.example.yml index 85dc5a5a972b..96095d1b6e44 100644 --- a/docker-compose.override.example.yml +++ b/docker-compose.override.example.yml @@ -8,7 +8,7 @@ services: - "${PORT:-3000}:3000" frontend: - # use these ports to be able to access the stack under http://localhost:3000 + # use these ports to be able to access the stack under http://localhost:4200 ports: - "${FE_PORT:-4200}:4200" diff --git a/docs/contributions-guide/README.md b/docs/contributions-guide/README.md new file mode 100644 index 000000000000..82fe61ba24d1 --- /dev/null +++ b/docs/contributions-guide/README.md @@ -0,0 +1,37 @@ +--- +sidebar_navigation: + title: Contributions guide + priority: 956 +description: OpenProject contributions guide. +keywords: contributions guide, translate OpenProject, contribute, community, documentation +--- + +# Contributions guide + +Welcome to the OpenProject **Contributions guide**. + +This platform thrives on the support of its Community, and there are many ways you can help make it even better. Whether you're a developer, a user, or simply someone passionate about improving open-source tools, your contributions matter. + +## Overview + +| Topic | Content | +| ------------------------------------------------------------ | :-------------------------------------------------------- | +| [Contribute to documentation](contribution-documentation) | Learn how you can contribute to OpenProject documentation | +| [Translate OpenProject](translate-openproject) | Help translate OpenProject | +| [Give back to Community](give-back-to-community) | Learn how you can support OpenProject Community | +| [Report a bug](../development/report-a-bug/) | Learn how to report a bug in OpenProject | +| [Submit a feature idea](../development/submit-feature-idea/) | See how you can submit an new feature idea | + + +## How can you help? + +### Contribute to Documentation +Help keep our documentation accurate, comprehensive, and user-friendly. Whether it's fixing typos, updating outdated information, or adding new sections, your contributions make a difference. [Learn more](./contribution-documentation/). + +### Translate OpenProject +Assist in making OpenProject accessible in more languages or refine existing translations. Your input ensures that users worldwide have a seamless experience. [Learn more](./translate-openproject/). + +### Support the Community +Share your experiences by leaving a review, sharing our blog articles, or promoting OpenProject in your network. Every small effort helps expand our Community. [Learn more](./give-back-to-community). + +Please also refer to the [Development guide](../development) for Code review guidelines. diff --git a/docs/contributions-guide/contribution-documentation/README.md b/docs/contributions-guide/contribution-documentation/README.md new file mode 100644 index 000000000000..98da92ecbe73 --- /dev/null +++ b/docs/contributions-guide/contribution-documentation/README.md @@ -0,0 +1,62 @@ +--- +sidebar_navigation: + title: Contribute to documentation + priority: 999 +description: Overview of the OpenProject documentation +keywords: contribution, documentation, documentation process +--- + +# Contribute to the OpenProject documentation + +High-quality documentation is essential for the success of OpenProject. It ensures that users, developers, and administrators can effectively understand and use the platform. + +By contributing to the documentation, you help: + +- Keep the content accurate and up-to-date. +- Make complex features easier to understand. +- Ensure that OpenProject is accessible to a wider audience. + +Whether it’s fixing typos, clarifying instructions, or adding missing sections, every contribution is valuable. This guide will walk you through the process of contributing to our documentation, ensuring your work is effective and aligned with OpenProject’s standards. + +## Overview + +| Topic | Content | +| ------------------------------------------------------ | :----------------------------------------------------------- | +| [Documentation process](documentation-process) | A step-by-step guide on how to contribute to the documentation. | +| [Documentation style guide](documentation-style-guide) | What are the styles and other requirements to follow when contributing to the documentation? | +| [Contribution support](contribution-support) | What to do if you need help regarding your contribution to the documentation? | + +With this guide for contributing to the OpenProject documentation we followed and took inspiration from the [Contribute to GitLab guide](https://about.gitlab.com/community/contribute/). + +> [!TIP] +> Documentation changes are **not** changes or additions to the code of the OpenProject application. For contributions to the code, see our [product development guide](../../development/product-development-handbook/). Please also refer to the [development guide](../../development/code-review-guidelines/) for Code review guidelines + +## OpenProject documentation overview + +The OpenProject documentation provides comprehensive resources, including user guides, system administration, installation and operation instructions, API references, development guides, and release notes. It is available online at [OpenProject Documentation](https://www.openproject.org/docs/), and can also be accessed directly from your OpenProject application via the **Help** menu (question mark icon in the top-right corner). + +The documentation supports current and future users by offering step-by-step instructions for setup, configuration, and integration of OpenProject. It also provides detailed use cases, feature guides, and instructions for using OpenProject alongside other applications. + +To ensure clarity, accuracy, and completeness, the documentation is continuously updated to reflect new features, improvements, and Community feedback. + +### Contributing to OpenProject Documentation + +As an open-source platform, OpenProject thrives on Community collaboration. Contributions to our documentation are open to everyone, whether you're a developer, a long-time user, or someone new to the platform. + +By contributing to the documentation, you help ensure it remains accurate, clear, and up-to-date, benefiting the entire OpenProject Community. We welcome contributions of all kinds, including improving existing content, fixing errors, and adding new sections. + +## Contribution guidelines for documentation + +### Report it via e-mail + +If you would like to report a typo or an inconsistency in our documentation, the quickest way to do it is to **write an e-mail** to [support@openproject.com](mailto:support@openproject.com) and describe it with as much detail an possible. + +### Let us know through the OpenProject Community installation + +You can also join us directly through the [OpenProject Community installation](https://community.openproject.org). Here you can **leave a message on the [Forum](https://community.openproject.org/projects/openproject/forums)**, or **create a work package with the type *Documentation* ** and describe or request a documentation change. + +To gain access to the Community installation at, please send an email to [support@openproject.com](mailto:support@openproject.com) with the subject line **"Joining Community"**. Our team will review your request and send you an invitation as soon as possible. + +### Write your own pull request + +If you prefer, you can also go ahead and create a **draft pull request** with your suggestions and ask for the review from our team. Please take a look at the [documentation process](./documentation-process/) for the exact steps to do that and consult the [documentation style guide](./documentation-style-guide/) for instructions on documentation formatting in OpenProject. diff --git a/docs/development/contribution-documentation/contribution-support/README.md b/docs/contributions-guide/contribution-documentation/contribution-support/README.md similarity index 100% rename from docs/development/contribution-documentation/contribution-support/README.md rename to docs/contributions-guide/contribution-documentation/contribution-support/README.md diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/README.md b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/README.md similarity index 91% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/README.md rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/README.md index f3d2f091716c..66a91125bc08 100644 --- a/docs/development/contribution-documentation/documentation-process-internal-contributor/README.md +++ b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/README.md @@ -12,10 +12,10 @@ This guide describes how internal team members with write permissions can contri ## Prerequisites -1. [The contributor has a user account on GitHub.com](../../../development/contribution-documentation/documentation-process/#step-1-create-user-account-on-githubcom) +1. [The contributor has a user account on GitHub.com](../documentation-process/#step-1-create-user-account-on-githubcom) 2. The contributor has write permissions on the [OpenProject repository](https://github.com/opf/openproject) -3. [Software Typora editor installed](../../../development/contribution-documentation/documentation-process/#step-2-install-typora) -4. [Software GitHub Desktop installed](../../../development/contribution-documentation/documentation-process/#step-3-install-github-desktop) +3. [Software Typora editor installed](../documentation-process/#step-2-install-typora) +4. [Software GitHub Desktop installed](../documentation-process/#step-3-install-github-desktop) ## Step 1: Clone the OpenProject repository in GitHub Desktop diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/add-documentation-label-pull-requests.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/add-documentation-label-pull-requests.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/add-documentation-label-pull-requests.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/add-documentation-label-pull-requests.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/clone-repository.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/clone-repository.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/clone-repository.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/clone-repository.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/commit-history-in-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/commit-history-in-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/commit-history-in-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/commit-history-in-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/comparing-changes.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/comparing-changes.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/comparing-changes.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/comparing-changes.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-1.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-1.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-1.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-1.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-2.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-2.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-2.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-new-branch-step-2.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/create-pull-request-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-pull-request-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/create-pull-request-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/create-pull-request-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/fetch-origin-in-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/fetch-origin-in-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/fetch-origin-in-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/fetch-origin-in-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/push-origin-in-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/push-origin-in-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/push-origin-in-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/push-origin-in-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/select-cloned-repository.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/select-cloned-repository.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/select-cloned-repository.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/select-cloned-repository.png diff --git a/docs/development/contribution-documentation/documentation-process-internal-contributor/select-reviewer-for-documentation.png b/docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/select-reviewer-for-documentation.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process-internal-contributor/select-reviewer-for-documentation.png rename to docs/contributions-guide/contribution-documentation/documentation-process-internal-contributor/select-reviewer-for-documentation.png diff --git a/docs/development/contribution-documentation/documentation-process/Create-draft-pull-request.png b/docs/contributions-guide/contribution-documentation/documentation-process/Create-draft-pull-request.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/Create-draft-pull-request.png rename to docs/contributions-guide/contribution-documentation/documentation-process/Create-draft-pull-request.png diff --git a/docs/development/contribution-documentation/documentation-process/README.md b/docs/contributions-guide/contribution-documentation/documentation-process/README.md similarity index 100% rename from docs/development/contribution-documentation/documentation-process/README.md rename to docs/contributions-guide/contribution-documentation/documentation-process/README.md diff --git a/docs/development/contribution-documentation/documentation-process/Ready-for-review.png b/docs/contributions-guide/contribution-documentation/documentation-process/Ready-for-review.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/Ready-for-review.png rename to docs/contributions-guide/contribution-documentation/documentation-process/Ready-for-review.png diff --git a/docs/development/contribution-documentation/documentation-process/add-documentation-label-pull-requests.png b/docs/contributions-guide/contribution-documentation/documentation-process/add-documentation-label-pull-requests.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/add-documentation-label-pull-requests.png rename to docs/contributions-guide/contribution-documentation/documentation-process/add-documentation-label-pull-requests.png diff --git a/docs/development/contribution-documentation/documentation-process/branches-drop-down-list.png b/docs/contributions-guide/contribution-documentation/documentation-process/branches-drop-down-list.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/branches-drop-down-list.png rename to docs/contributions-guide/contribution-documentation/documentation-process/branches-drop-down-list.png diff --git a/docs/development/contribution-documentation/documentation-process/clone-repository.png b/docs/contributions-guide/contribution-documentation/documentation-process/clone-repository.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/clone-repository.png rename to docs/contributions-guide/contribution-documentation/documentation-process/clone-repository.png diff --git a/docs/development/contribution-documentation/documentation-process/commit-history-in-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process/commit-history-in-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/commit-history-in-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process/commit-history-in-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process/continue-sign-in-in-browser.png b/docs/contributions-guide/contribution-documentation/documentation-process/continue-sign-in-in-browser.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/continue-sign-in-in-browser.png rename to docs/contributions-guide/contribution-documentation/documentation-process/continue-sign-in-in-browser.png diff --git a/docs/development/contribution-documentation/documentation-process/create-new-branch-step-1.png b/docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-1.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/create-new-branch-step-1.png rename to docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-1.png diff --git a/docs/development/contribution-documentation/documentation-process/create-new-branch-step-2.png b/docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-2.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/create-new-branch-step-2.png rename to docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-2.png diff --git a/docs/development/contribution-documentation/documentation-process/create-new-branch-step-3.png b/docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-3.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/create-new-branch-step-3.png rename to docs/contributions-guide/contribution-documentation/documentation-process/create-new-branch-step-3.png diff --git a/docs/development/contribution-documentation/documentation-process/create-pull-request-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process/create-pull-request-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/create-pull-request-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process/create-pull-request-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process/define-how-to-use-the-fork.png b/docs/contributions-guide/contribution-documentation/documentation-process/define-how-to-use-the-fork.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/define-how-to-use-the-fork.png rename to docs/contributions-guide/contribution-documentation/documentation-process/define-how-to-use-the-fork.png diff --git a/docs/development/contribution-documentation/documentation-process/disable-github-actions.png b/docs/contributions-guide/contribution-documentation/documentation-process/disable-github-actions.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/disable-github-actions.png rename to docs/contributions-guide/contribution-documentation/documentation-process/disable-github-actions.png diff --git a/docs/development/contribution-documentation/documentation-process/edit-the-pull-request.png b/docs/contributions-guide/contribution-documentation/documentation-process/edit-the-pull-request.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/edit-the-pull-request.png rename to docs/contributions-guide/contribution-documentation/documentation-process/edit-the-pull-request.png diff --git a/docs/development/contribution-documentation/documentation-process/fork-openproject.png b/docs/contributions-guide/contribution-documentation/documentation-process/fork-openproject.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/fork-openproject.png rename to docs/contributions-guide/contribution-documentation/documentation-process/fork-openproject.png diff --git a/docs/development/contribution-documentation/documentation-process/open-pr-in-forked-repository.png b/docs/contributions-guide/contribution-documentation/documentation-process/open-pr-in-forked-repository.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/open-pr-in-forked-repository.png rename to docs/contributions-guide/contribution-documentation/documentation-process/open-pr-in-forked-repository.png diff --git a/docs/development/contribution-documentation/documentation-process/pull-upstream-changes.png b/docs/contributions-guide/contribution-documentation/documentation-process/pull-upstream-changes.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/pull-upstream-changes.png rename to docs/contributions-guide/contribution-documentation/documentation-process/pull-upstream-changes.png diff --git a/docs/development/contribution-documentation/documentation-process/push-origin-in-github-desktop.png b/docs/contributions-guide/contribution-documentation/documentation-process/push-origin-in-github-desktop.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/push-origin-in-github-desktop.png rename to docs/contributions-guide/contribution-documentation/documentation-process/push-origin-in-github-desktop.png diff --git a/docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-1.png b/docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-1.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-1.png rename to docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-1.png diff --git a/docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-2.png b/docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-2.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-2.png rename to docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-2.png diff --git a/docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-3.png b/docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-3.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-3.png rename to docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-3.png diff --git a/docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-4.png b/docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-4.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/rebase-your-fork-step-4.png rename to docs/contributions-guide/contribution-documentation/documentation-process/rebase-your-fork-step-4.png diff --git a/docs/development/contribution-documentation/documentation-process/select-repository-to-be-cloned.png b/docs/contributions-guide/contribution-documentation/documentation-process/select-repository-to-be-cloned.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/select-repository-to-be-cloned.png rename to docs/contributions-guide/contribution-documentation/documentation-process/select-repository-to-be-cloned.png diff --git a/docs/development/contribution-documentation/documentation-process/select-reviewer-for-documentation.png b/docs/contributions-guide/contribution-documentation/documentation-process/select-reviewer-for-documentation.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/select-reviewer-for-documentation.png rename to docs/contributions-guide/contribution-documentation/documentation-process/select-reviewer-for-documentation.png diff --git a/docs/development/contribution-documentation/documentation-process/select-the-new-release-branch.png b/docs/contributions-guide/contribution-documentation/documentation-process/select-the-new-release-branch.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/select-the-new-release-branch.png rename to docs/contributions-guide/contribution-documentation/documentation-process/select-the-new-release-branch.png diff --git a/docs/development/contribution-documentation/documentation-process/sign-in-into-github.png b/docs/contributions-guide/contribution-documentation/documentation-process/sign-in-into-github.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/sign-in-into-github.png rename to docs/contributions-guide/contribution-documentation/documentation-process/sign-in-into-github.png diff --git a/docs/development/contribution-documentation/documentation-process/switch-the-default-branch-step-1.png b/docs/contributions-guide/contribution-documentation/documentation-process/switch-the-default-branch-step-1.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/switch-the-default-branch-step-1.png rename to docs/contributions-guide/contribution-documentation/documentation-process/switch-the-default-branch-step-1.png diff --git a/docs/development/contribution-documentation/documentation-process/switch-the-default-branch-step-2.png b/docs/contributions-guide/contribution-documentation/documentation-process/switch-the-default-branch-step-2.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/switch-the-default-branch-step-2.png rename to docs/contributions-guide/contribution-documentation/documentation-process/switch-the-default-branch-step-2.png diff --git a/docs/development/contribution-documentation/documentation-process/sync-fork-update-branch.png b/docs/contributions-guide/contribution-documentation/documentation-process/sync-fork-update-branch.png similarity index 100% rename from docs/development/contribution-documentation/documentation-process/sync-fork-update-branch.png rename to docs/contributions-guide/contribution-documentation/documentation-process/sync-fork-update-branch.png diff --git a/docs/development/contribution-documentation/documentation-style-guide/OpenProject_documentation_menu.png b/docs/contributions-guide/contribution-documentation/documentation-style-guide/OpenProject_documentation_menu.png similarity index 100% rename from docs/development/contribution-documentation/documentation-style-guide/OpenProject_documentation_menu.png rename to docs/contributions-guide/contribution-documentation/documentation-style-guide/OpenProject_documentation_menu.png diff --git a/docs/development/contribution-documentation/documentation-style-guide/README.md b/docs/contributions-guide/contribution-documentation/documentation-style-guide/README.md similarity index 100% rename from docs/development/contribution-documentation/documentation-style-guide/README.md rename to docs/contributions-guide/contribution-documentation/documentation-style-guide/README.md diff --git a/docs/development/contribution-documentation/documentation-style-guide/screenshot_area_highlight.png b/docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_area_highlight.png similarity index 100% rename from docs/development/contribution-documentation/documentation-style-guide/screenshot_area_highlight.png rename to docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_area_highlight.png diff --git a/docs/development/contribution-documentation/documentation-style-guide/screenshot_highlights_example.png b/docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_highlights_example.png similarity index 100% rename from docs/development/contribution-documentation/documentation-style-guide/screenshot_highlights_example.png rename to docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_highlights_example.png diff --git a/docs/development/contribution-documentation/documentation-style-guide/screenshot_numberedlabels_highlight.png b/docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_numberedlabels_highlight.png similarity index 100% rename from docs/development/contribution-documentation/documentation-style-guide/screenshot_numberedlabels_highlight.png rename to docs/contributions-guide/contribution-documentation/documentation-style-guide/screenshot_numberedlabels_highlight.png diff --git a/docs/contributions-guide/give-back-to-community/README.md b/docs/contributions-guide/give-back-to-community/README.md new file mode 100644 index 000000000000..34ba45efe638 --- /dev/null +++ b/docs/contributions-guide/give-back-to-community/README.md @@ -0,0 +1,71 @@ +--- +sidebar_navigation: + title: Give back to Community + priority: 700 +description: Guide on how to contribute to OpenProject Community +keywords: community, review, video, blog, contribution +--- + +# Give back to Community + +OpenProject thrives on the strength and engagement of its Community. Beyond coding or documentation, there are many ways you can help spread the word, share your experiences, and inspire others to adopt and use OpenProject effectively. + +## Share your experience + +> [!IMPORTANT] +> By submitting your content, we assume that you grant us permission to publish it on our platforms. If you would like to review the content before it is published, please let us know explicitly, and we will be happy to accommodate your request. + +Your unique experience with OpenProject can inspire others and provide valuable feedback to our team. Here are some ideas for sharing your story: + +- **Written Texts** + Create a brief article (0.5–2 pages) that covers the following topics: + + - A short introduction of yourself and your project. + - Basic details such as the project’s duration, objectives, and participants. + - An overview of which OpenProject modules you used and how they supported your work. + - Your overall experience: What works well for you? What could be improved? + - Include screenshots showcasing your OpenProject setup (with permission to publish). + + **Example**: take a look one one our case studies with an [NGO](https://www.openproject.org/project-management-ngos-foundations/case-study-open-source-initiative-osi/) , a [university project](https://www.openproject.org/project-management-universities-research/case-study-rewrite/) or a [municipality](https://www.openproject.org/project-management-public-sector/case-study-city-ravensburg/). + +- **Blog Posts** + Write a detailed blog post about your OpenProject journey. You can either: + + - Submit your blog post for publication on the OpenProject website. [Contact us](https://www.openproject.org/contact/) for guidelines. + - Publish it on your own channels (website, blog, or LinkedIn) and link back to OpenProject. + +- **Video Contributions** + Record a short video explaining how you or your team use OpenProject. Include key features, workflows, and tips that others might find useful. + +- **Testimonials and Quotes** + Share a short testimonial or quote about your experience with OpenProject. If possible, **include a professional photo** of yourself or your team for publication. + +## Support us online + +Help OpenProject reach more users by engaging with our content and sharing it with your network. + +- **Social Media Posts** + - Share your experience with OpenProject on your social channels, including a link to our website. + - You can also re-share content from the [OpenProject blog](https://www.openproject.org/blog/) or our official social media accounts. + + [Follow us on LinkedIn](https://www.linkedin.com/company/openproject-gmbh) + + [Follow us on Reddit](https://www.reddit.com/r/openproject) + + [Follow us on Fosstodon](https://fosstodon.org/@openproject) + + [Follow us on Twitter/X](https://twitter.com/openproject) + + [Follow us on Bluesky](https://bsky.app/profile/openproject.bsky.social) +- **Add a Backlink to Your Website** + Include a link to OpenProject’s website on your blog, portfolio, or company website to help more people discover our tool. +- **Write a Review** + Share your thoughts about OpenProject on one of our review platforms. [Leave a review ](https://www.openproject.org/reviews/). + +## Spread the word + +Let your network know how OpenProject has made an impact for you. Whether it’s during a team meeting, at an industry event, or simply in a conversation with a peer, sharing your experiences can inspire others to explore OpenProject. + +## Join our Community + +Be part of the conversation! **Join us in our weekly team call** and share how you use OpenProject in your team or organization. diff --git a/docs/contributions-guide/give-back-to-community/openproject-user-guide-create-project-plan.gif b/docs/contributions-guide/give-back-to-community/openproject-user-guide-create-project-plan.gif new file mode 100644 index 000000000000..86bdb3c00e5b Binary files /dev/null and b/docs/contributions-guide/give-back-to-community/openproject-user-guide-create-project-plan.gif differ diff --git a/docs/contributions-guide/give-back-to-community/openproject-user-guide-edit-project-plan.gif b/docs/contributions-guide/give-back-to-community/openproject-user-guide-edit-project-plan.gif new file mode 100644 index 000000000000..1e3e21b26a77 Binary files /dev/null and b/docs/contributions-guide/give-back-to-community/openproject-user-guide-edit-project-plan.gif differ diff --git a/docs/contributions-guide/give-back-to-community/openproject-user-guide-select-gantt-charts-module.png b/docs/contributions-guide/give-back-to-community/openproject-user-guide-select-gantt-charts-module.png new file mode 100644 index 000000000000..466e10d66b95 Binary files /dev/null and b/docs/contributions-guide/give-back-to-community/openproject-user-guide-select-gantt-charts-module.png differ diff --git a/docs/development/translate-openproject/GitHub-CrowdIn-OP.png b/docs/contributions-guide/translate-openproject/GitHub-CrowdIn-OP.png similarity index 100% rename from docs/development/translate-openproject/GitHub-CrowdIn-OP.png rename to docs/contributions-guide/translate-openproject/GitHub-CrowdIn-OP.png diff --git a/docs/development/translate-openproject/README.md b/docs/contributions-guide/translate-openproject/README.md similarity index 98% rename from docs/development/translate-openproject/README.md rename to docs/contributions-guide/translate-openproject/README.md index f41c4c8913e9..f3976cf99638 100644 --- a/docs/development/translate-openproject/README.md +++ b/docs/contributions-guide/translate-openproject/README.md @@ -1,7 +1,7 @@ --- sidebar_navigation: title: Translate OpenProject - priority: 985 + priority: 800 description: How to translate OpenProject to your language keywords: translation, translate, crowdin, localization --- @@ -64,4 +64,4 @@ If you are interested in becoming a proof reader, please contact one of the proj If your language is not listed in the list of CrowdIn languages, please contact our project managers or send us an email so we can add your language. -Find out more about our development concepts regarding translations [here](../concepts/translations). +Find out more about our development concepts regarding translations [here](../../development/concepts/translations). diff --git a/docs/development/translate-openproject/crowdin-editor.png b/docs/contributions-guide/translate-openproject/crowdin-editor.png similarity index 100% rename from docs/development/translate-openproject/crowdin-editor.png rename to docs/contributions-guide/translate-openproject/crowdin-editor.png diff --git a/docs/development/translate-openproject/crowdin-language-overview.png b/docs/contributions-guide/translate-openproject/crowdin-language-overview.png similarity index 100% rename from docs/development/translate-openproject/crowdin-language-overview.png rename to docs/contributions-guide/translate-openproject/crowdin-language-overview.png diff --git a/docs/development/translate-openproject/crowdin-multi-translation.png b/docs/contributions-guide/translate-openproject/crowdin-multi-translation.png similarity index 100% rename from docs/development/translate-openproject/crowdin-multi-translation.png rename to docs/contributions-guide/translate-openproject/crowdin-multi-translation.png diff --git a/docs/development/translate-openproject/crowdin-overview.png b/docs/contributions-guide/translate-openproject/crowdin-overview.png similarity index 100% rename from docs/development/translate-openproject/crowdin-overview.png rename to docs/contributions-guide/translate-openproject/crowdin-overview.png diff --git a/docs/development/translate-openproject/fair-language/README.md b/docs/contributions-guide/translate-openproject/fair-language/README.md similarity index 100% rename from docs/development/translate-openproject/fair-language/README.md rename to docs/contributions-guide/translate-openproject/fair-language/README.md diff --git a/docs/development/contribution-documentation/README.md b/docs/development/contribution-documentation/README.md deleted file mode 100644 index 39055ad8e01f..000000000000 --- a/docs/development/contribution-documentation/README.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_navigation: - title: Contribute to documentation - priority: 999 -description: Overview of the OpenProject documentation -keywords: contribution, documentation, documentation process ---- - -# Contribute to the OpenProject documentation - -## How do I get access to the OpenProject Community installation? - -To get an account for community.openproject.org, please write an email with the subject 'Joining community' to [support@openproject.com](mailto:support@openproject.com), if you would like to join our Community. We will then invite you as soon as possible. - -## What does the OpenProject documentation entail? - -The OpenProject documentation makes available user guides, system admin, installation and operation, API as well as development guides and release notes. - -Current and future users of the application find instructions for the set up and configuration of OpenProject in the documentation. Furthermore, it contains use cases and usage instructions for all OpenProject features and the use of OpenProject with other applications. - -This documentation evolves continuously with new features and improvements to achieve clarity, accuracy, and completeness. - -## Where to find the OpenProject documentation? - -The documentation for OpenProject is published [here](https://www.openproject.org/docs/). You can also access the documentation from your OpenProject application under user guides and API documentation below the question mark at the top right in the header menu. - -## Who can contribute to the documentation? - -Being proudly open source, we invite anyone in our community to contribute to our software as well as the documentation to improve it even further. - -## What can you contribute to the documentation? - -Documentation improvements and changes apply to the documentation described in the section above. Documentation changes are **not** changes or additions to the code of the OpenProject application. For contributions to the code, see our [product development guide](../product-development-handbook/). - -We are looking forward to receiving the following contributions from you: - -- Eliminating errors and working on other improvements in the existing documentation. This could be missing content, e.g. descriptions or step-by-step guides for features. - -- Adding use cases. - -- Adding visuals, e.g. screenshots, to complement descriptions. - -- Fixing of spelling, grammar, punctuation mistakes. - -- Fixing of internal and external links that are not working or are incorrect. - -- Translations into your mother tongue (coming soon). - -## Overview - -| Topic | Content | -| ------------------------------------------------------ | :----------------------------------------------------------- | -| [Documentation process](documentation-process) | A step-by-step guide on how to contribute to the documentation. | -| [Documentation style guide](documentation-style-guide) | What are the styles and other requirements to follow when contributing to the documentation? | -| [Contribution support](contribution-support) | What to do if you need help regarding your contribution to the documentation? | - -With this guide for contributing to the OpenProject documentation we followed and took inspiration from the [Contribute to GitLab guide](https://about.gitlab.com/community/contribute/). diff --git a/docs/faq/README.md b/docs/faq/README.md index fe32c403a21b..462983058f16 100644 --- a/docs/faq/README.md +++ b/docs/faq/README.md @@ -296,7 +296,7 @@ OpenProject changed the database from MySQL (rarely also MariaDB) in older Versi ### How can I contribute to OpenProject? -We welcome everybody willing to help make OpenProject better. There are a lot of possibilities for helping, be it [improving the translations](../development/translate-openproject) via crowdin, answering questions in the [forums](https://community.openproject.org/projects/openproject/forums) or by fixing bugs and implementing features. +We welcome everybody willing to help make OpenProject better. There are a lot of possibilities for helping, be it [improving the translations](../contributions-guide/translate-openproject) via crowdin, answering questions in the [forums](https://community.openproject.org/projects/openproject/forums) or by fixing bugs and implementing features. If you want to code, a good starting point would be to make yourself familiar with the [basic approaches for developing](../development/) in OpenProject and opening a pull request on GitHub referencing an existing bug report or feature request. Find our GitHub page [here](https://github.com/opf/openproject). diff --git a/docs/release-notes/12/12-0-7/README.md b/docs/release-notes/12/12-0-7/README.md index 4baf3e484be6..667a1c216eef 100644 --- a/docs/release-notes/12/12-0-7/README.md +++ b/docs/release-notes/12/12-0-7/README.md @@ -23,7 +23,7 @@ There is now a separate setting for the max API size that will be used for these OpenProject relies on community translations for some languages that we cannot provide translations for ourselves. It was brought to our attention that the Russian translations partially contain expletive languages. Thanks to community contributors Sergey and Christina, these translations were fixed on crowdin and could now be included into the release. -We need your help to improve and extend translations of OpenProject into your native language. To get more information, please see our [Translating OpenProject guide](../../../development/translate-openproject/) and our [project on crowdin.com](https://crowdin.com/project/openproject), where you can provide and help approve translations from your browser. If you wish to become a proofreader for your language, please reach out to [info@openproject.com](mailto:info@openproject.com) +We need your help to improve and extend translations of OpenProject into your native language. To get more information, please see our [Translating OpenProject guide](../../../contributions-guide/translate-openproject/) and our [project on crowdin.com](https://crowdin.com/project/openproject), where you can provide and help approve translations from your browser. If you wish to become a proofreader for your language, please reach out to [info@openproject.com](mailto:info@openproject.com) ## Custom plugins in packaged installations diff --git a/docs/release-notes/13-4-0/README.md b/docs/release-notes/13-4-0/README.md index e23756b496e8..39f389fa5280 100644 --- a/docs/release-notes/13-4-0/README.md +++ b/docs/release-notes/13-4-0/README.md @@ -173,4 +173,4 @@ An important part is also the translations, for which we thank the following con - [Marek Bajon](https://crowdin.com/profile/mbajon), who translated to Polish - [Vlastislav Dockal](https://crowdin.com/profile/vdockal), who translated to Czech -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-0-0/README.md b/docs/release-notes/14-0-0/README.md index 8d7b81dc0e3c..185f90706439 100644 --- a/docs/release-notes/14-0-0/README.md +++ b/docs/release-notes/14-0-0/README.md @@ -297,4 +297,4 @@ Silas Kropf, Philipp Schulz, Benjamin Rönnau, Mario Haustein, Matt User, Mario Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight user [izzahk](https://crowdin.com/profile/izzahk) who has done an outstanding number of translations for the Malaysian language in recent weeks. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-1-0/README.md b/docs/release-notes/14-1-0/README.md index aa2488be3e18..21f6ca66ec98 100644 --- a/docs/release-notes/14-1-0/README.md +++ b/docs/release-notes/14-1-0/README.md @@ -190,4 +190,4 @@ Also thanks for finding and responsibly disclosing the CVE-2024-135224 vulnerabi Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight user [Syvert](https://crowdin.com/profile/syvert) who has done an outstanding number of translations for the Norwegian language in recent weeks. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-2-0/README.md b/docs/release-notes/14-2-0/README.md index 851475f6828f..195801e4fd78 100644 --- a/docs/release-notes/14-2-0/README.md +++ b/docs/release-notes/14-2-0/README.md @@ -125,4 +125,4 @@ A very special thank you goes to the City of Cologne for sponsoring features on Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight user [aniessalam](https://crowdin.com/profile/aniessalam) who has done an outstanding number of translations for the Malay language in recent weeks. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-3-0/README.md b/docs/release-notes/14-3-0/README.md index e5fd4a780e53..23a9739840c8 100644 --- a/docs/release-notes/14-3-0/README.md +++ b/docs/release-notes/14-3-0/README.md @@ -192,4 +192,4 @@ A thank you also goes to Eric Guo for contributing the Date zoom based on calend Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight user [Todor Belov](https://crowdin.com/profile/todor.belov), who has done an outstanding number of translations for the Bulgarian language in recent weeks. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-5-0/README.md b/docs/release-notes/14-5-0/README.md index cb0b018c7fce..b86f02b92407 100644 --- a/docs/release-notes/14-5-0/README.md +++ b/docs/release-notes/14-5-0/README.md @@ -214,4 +214,4 @@ Last but not least, we are very grateful for our very engaged translation contri - [Sebvita_devinci](https://crowdin.com/profile/sebvita_devinci), for proof reading French translations. - [Alin Marcu](https://crowdin.com/profile/deconfcom), for proof reading Romanian translations. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/14-6-0/README.md b/docs/release-notes/14-6-0/README.md index 1e7eb256be9e..3efd7683c6c6 100644 --- a/docs/release-notes/14-6-0/README.md +++ b/docs/release-notes/14-6-0/README.md @@ -143,4 +143,4 @@ Last but not least, we are very grateful for our very engaged translation contri - [BigSeung](https://crowdin.com/profile/BigSeung), for translations into Korean. - [Raffaele Brevetti](https://crowdin.com/profile/rbrevetti), for translations into Italian. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/release-notes/15-0-0/README.md b/docs/release-notes/15-0-0/README.md index 7af154f24e35..809db56fbba1 100644 --- a/docs/release-notes/15-0-0/README.md +++ b/docs/release-notes/15-0-0/README.md @@ -235,4 +235,4 @@ Last but not least, we are very grateful for our very engaged translation contri - [hmmftg](https://crowdin.com/profile/hmmftg), for a great number of translations into Persian. - [william](https://crowdin.com/profile/WilliamFromTW), for a great number of translations into Chinese Simplified and Chinese Traditional. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! diff --git a/docs/system-admin-guide/system-settings/languages/README.md b/docs/system-admin-guide/system-settings/languages/README.md index d9ce3c840d3e..a3cf4db9b564 100644 --- a/docs/system-admin-guide/system-settings/languages/README.md +++ b/docs/system-admin-guide/system-settings/languages/README.md @@ -14,6 +14,6 @@ The Languages page lets you select languages you would like to activate from the At the moment there are over 30 languages available. > [!NOTE] -> Many languages are translated by the community. We highly appreciate if you want to [help translating OpenProject to your language](../../../development/translate-openproject). +> Many languages are translated by the community. We highly appreciate if you want to [help translating OpenProject to your language](../../../contributions-guide/translate-openproject). You can [choose your language in your user profile](../../../user-guide/account-settings/#change-your-language). diff --git a/frontend/src/app/core/global_search/input/global-search-input.component.html b/frontend/src/app/core/global_search/input/global-search-input.component.html index 86954b119595..0419ceecb2e0 100644 --- a/frontend/src/app/core/global_search/input/global-search-input.component.html +++ b/frontend/src/app/core/global_search/input/global-search-input.component.html @@ -59,6 +59,7 @@
diff --git a/frontend/src/app/core/path-helper/path-helper.service.ts b/frontend/src/app/core/path-helper/path-helper.service.ts index 667384921ef1..b39afd827d8f 100644 --- a/frontend/src/app/core/path-helper/path-helper.service.ts +++ b/frontend/src/app/core/path-helper/path-helper.service.ts @@ -220,6 +220,10 @@ export class PathHelperService { return `${this.usersPath()}/${id}`; } + public userHoverCardPath(id:string|number) { + return `${this.usersPath()}/${id}/hover_card`; + } + public placeholderUserPath(id:string|number) { return `${this.placeholderUsersPath()}/${id}`; } diff --git a/frontend/src/app/core/setup/globals/global-listeners/hover-card-trigger.service.ts b/frontend/src/app/core/setup/globals/global-listeners/hover-card-trigger.service.ts index eff95f2b842c..6edea8a379ff 100644 --- a/frontend/src/app/core/setup/globals/global-listeners/hover-card-trigger.service.ts +++ b/frontend/src/app/core/setup/globals/global-listeners/hover-card-trigger.service.ts @@ -29,12 +29,23 @@ import { Injectable, Injector, NgZone } from '@angular/core'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; import { HoverCardComponent } from 'core-app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal'; +import { PortalOutletTarget } from 'core-app/shared/components/modal/portal-outlet-target.enum'; @Injectable({ providedIn: 'root' }) export class HoverCardTriggerService { private modalElement:HTMLElement; private mouseInModal = false; + private hoverTimeout:number|null = null; + private closeTimeout:number|null = null; + // Set to custom when opening the hover card on top of another modal + private modalTarget:PortalOutletTarget = PortalOutletTarget.Default; + private previousTarget:HTMLElement|null = null; + + // The time you need to keep hovering over a trigger before the hover card is shown + OPEN_DELAY_IN_MS = 1000; + // The time you need to keep away from trigger/hover card before an opened card is closed + CLOSE_DELAY_IN_MS = 250; constructor( readonly opModalService:OpModalService, @@ -49,31 +60,45 @@ export class HoverCardTriggerService { e.stopPropagation(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const el = e.target as HTMLElement; - if (el) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const turboFrameUrl = el.getAttribute('data-hover-card-url'); - - if (!turboFrameUrl) { - return; - } - - this.opModalService.show( - HoverCardComponent, - this.injector, - { turboFrameSrc: turboFrameUrl, event: e }, - true, - ).subscribe((previewModal) => { - this.modalElement = previewModal.elementRef.nativeElement as HTMLElement; - void previewModal.reposition(this.modalElement, el); - }); + if (!el) { return; } + + if (this.previousTarget && this.previousTarget === el) { + // Re-entering the trigger counts as hovering over the card: + this.mouseInModal = true; + // But we will not re-render the same card, abort here + return; + } + + // Hovering over a new target. Close the old one (if any). + this.close(true); + this.previousTarget = el; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const turboFrameUrl = el.getAttribute('data-hover-card-url'); + if (!turboFrameUrl) { return; } + + // Reset close timer for when hovering over multiple triggers in quick succession. + // A timer from a previous hover card might still be running. We do not want it to + // close the new (i.e. this) hover card. + if (this.closeTimeout) { + clearTimeout(this.closeTimeout); + this.closeTimeout = null; } + + // Set a delay before showing the hover card + this.hoverTimeout = window.setTimeout(() => { + this.showHoverCard(el, turboFrameUrl, e); + }, this.OPEN_DELAY_IN_MS); }); jQuery(document.body).on('mouseleave', '.op-hover-card--preview-trigger', () => { + this.clearHoverTimer(); + this.mouseInModal = false; this.closeAfterTimeout(); }); jQuery(document.body).on('mouseleave', '.op-hover-card', () => { + this.clearHoverTimer(); this.mouseInModal = false; this.closeAfterTimeout(); }); @@ -83,13 +108,63 @@ export class HoverCardTriggerService { }); } + private showHoverCard(el:HTMLElement, turboFrameUrl:string, e:JQuery.MouseOverEvent) { + // Abort if the element is no longer present in the DOM. This can happen when this method is called after a delay. + if (!document.body.contains(el)) { return; } + + this.parseHoverCardOptions(el); + + // There is only one possible slot to insert a modal. If that slot is taken, we assume the other modal + // to be more important than a hover card and give up. + const modal = this.opModalService.showIfNotActive( + HoverCardComponent, + this.injector, + { turboFrameSrc: turboFrameUrl, event: e }, + true, + false, + this.modalTarget, + ); + + modal?.subscribe((previewModal) => { + this.modalElement = previewModal.elementRef.nativeElement as HTMLElement; + previewModal.alignment = 'top'; + + void previewModal.reposition(this.modalElement, el); + }); + } + + // Should be called when the mouse leaves the hover-zone so that we no longer attempt ot display the hover card. + private clearHoverTimer() { + if (this.hoverTimeout) { + clearTimeout(this.hoverTimeout); + this.hoverTimeout = null; + } + } + private closeAfterTimeout() { this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - if (!this.mouseInModal) { - this.opModalService.close(); - } - }, 100); + this.closeTimeout = window.setTimeout(() => { + this.close(); + }, this.CLOSE_DELAY_IN_MS); }); } + + private close(forceClose=false) { + if (forceClose) { + this.mouseInModal = false; + } + + if (!this.mouseInModal) { + this.opModalService.close(); + // Allow opening this target once more, since it has been orderly closed + this.previousTarget = null; + } + } + + private parseHoverCardOptions(el:HTMLElement) { + const modalTarget = el.getAttribute('data-hover-card-target'); + if (modalTarget) { + this.modalTarget = parseInt(modalTarget, 10); + } + } } diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts index b1f93d9f1718..ffd5323c82f9 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts @@ -93,7 +93,9 @@ export class WorkPackageRelationsComponent extends UntilDestroyedMixin implement .events$ .pipe( filter((e:RelatedWorkPackageEvent) => { - return e.eventType === 'association' && e.id.toString() === this.workPackage.id?.toString(); + return e.eventType === 'association' + && e.id.toString() === this.workPackage.id?.toString() + && e.relationType !== 'parent'; }), debounceTime(500), this.untilDestroyed(), diff --git a/frontend/src/app/features/work-packages/routing/split-view-routes.template.ts b/frontend/src/app/features/work-packages/routing/split-view-routes.template.ts index 83c9b26cb378..9b05b51e4761 100644 --- a/frontend/src/app/features/work-packages/routing/split-view-routes.template.ts +++ b/frontend/src/app/features/work-packages/routing/split-view-routes.template.ts @@ -117,7 +117,7 @@ export function makeSplitViewRoutes(baseRoute:string, // Remember the base route so we can route back to it anywhere baseRoute, parent: baseRoute, - mobileAlternative: showMobileAlternative ? 'work-packages.show' : undefined, + mobileAlternative: showMobileAlternative ? 'work-packages.new' : undefined, }, views: { // Retarget and by that override the grandparent views diff --git a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter-template.component.html b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter-template.component.html index 6e40633b218d..e7753cd2dfa6 100644 --- a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter-template.component.html +++ b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter-template.component.html @@ -7,6 +7,8 @@ *ngIf="item && item.href" [principal]="item" [hideName]="true" + [hoverCard]="hoverCards" + [hoverCardModalTarget]="isOpenedInModal ? 'custom' : 'default'" size="mini" > ; @ViewChild('footerTemplate') footerTemplate?:TemplateRef; diff --git a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts index 904b8f0afcc2..91cc6cd53fb0 100644 --- a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts +++ b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts @@ -56,6 +56,7 @@ import { addFiltersToPath } from 'core-app/core/apiv3/helpers/add-filters-to-pat import { UserAutocompleterTemplateComponent } from 'core-app/shared/components/autocompleter/user-autocompleter/user-autocompleter-template.component'; import { IUser } from 'core-app/core/state/principals/user.model'; import { compareByAttribute } from 'core-app/shared/helpers/angular/tracking-functions'; +import { SHOW_USER_HOVER_CARD } from 'core-app/shared/components/time_entries/create/create.modal'; export const usersAutocompleterSelector = 'op-user-autocompleter'; @@ -87,18 +88,31 @@ export interface IUserAutocompleteItem { export class UserAutocompleterComponent extends OpAutocompleterComponent implements OnInit, ControlValueAccessor { @Input() public inviteUserToProject:string|undefined; + @Input() public isOpenedInModal:boolean = false; + @Input() public hoverCards:boolean = true; + @Input() public url:string = this.apiV3Service.users.path; @Output() public userInvited = new EventEmitter(); @InjectField(OpInviteUserModalService) opInviteUserModalService:OpInviteUserModalService; + @InjectField(SHOW_USER_HOVER_CARD, true) showUserHoverCard:boolean; getOptionsFn = this.getAvailableUsers.bind(this); ngOnInit():void { super.ngOnInit(); - this.applyTemplates(UserAutocompleterTemplateComponent, { inviteUserToProject: this.inviteUserToProject }); + // Disabling hover cards by injection takes precedence over the input setting + if (!this.showUserHoverCard) { + this.hoverCards = false; + } + + this.applyTemplates(UserAutocompleterTemplateComponent, { + inviteUserToProject: this.inviteUserToProject, + isOpenedInModal: this.isOpenedInModal, + hoverCards: this.hoverCards, + }); this .opInviteUserModalService diff --git a/frontend/src/app/shared/components/fields/display/field-types/user-display-field.module.ts b/frontend/src/app/shared/components/fields/display/field-types/user-display-field.module.ts index c28ede70d080..d4b59e731063 100644 --- a/frontend/src/app/shared/components/fields/display/field-types/user-display-field.module.ts +++ b/frontend/src/app/shared/components/fields/display/field-types/user-display-field.module.ts @@ -29,13 +29,19 @@ import { DisplayField } from 'core-app/shared/components/fields/display/display-field.module'; import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator'; import { PrincipalRendererService } from 'core-app/shared/components/principal/principal-renderer.service'; +import { PrincipalLike } from 'core-app/shared/components/principal/principal-types'; + +interface Attribute { + url?:string; + name?:string; +} export class UserDisplayField extends DisplayField { @InjectField() principalRenderer:PrincipalRendererService; public get value() { if (this.schema) { - return this.attribute && this.attribute.name; + return this.typeSafeAttribute()?.name || ''; } return null; } @@ -46,10 +52,14 @@ export class UserDisplayField extends DisplayField { } else { this.principalRenderer.render( element, - this.attribute, + this.typeSafeAttribute() as PrincipalLike, { hide: false, link: false }, - { hide: false, size: 'medium' }, + { hide: false, size: 'medium', hoverCard: { url: this.typeSafeAttribute()?.url || '' } }, ); } } + + private typeSafeAttribute():Attribute { + return this.attribute as Attribute; + } } diff --git a/frontend/src/app/shared/components/modal/modal.service.ts b/frontend/src/app/shared/components/modal/modal.service.ts index c495e3d8a3dc..eacdd7b1ad32 100644 --- a/frontend/src/app/shared/components/modal/modal.service.ts +++ b/frontend/src/app/shared/components/modal/modal.service.ts @@ -67,6 +67,26 @@ export class OpModalService { }); } + /** + * Checks whether there is currently an active modal. Only shows the requested modal if this is not the case. + * Will return null if showing the modal is denied. + * @see show + */ + public showIfNotActive( + modal:ComponentType, + injector:Injector|'global', + locals:Record = {}, + notFullscreen = false, + mobileTopPosition = false, + target = PortalOutletTarget.Default, + ):Observable|null { + if (this.activeModalInstance$.value) { + return null; + } + + return this.show(modal, injector, locals, notFullscreen, mobileTopPosition, target); + } + /** * Open a Modal reference and append it to the portal * diff --git a/frontend/src/app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal.sass b/frontend/src/app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal.sass index e3d1db7067c5..fff585cb21ea 100644 --- a/frontend/src/app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal.sass +++ b/frontend/src/app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal.sass @@ -1,6 +1,13 @@ @import "helpers" +@keyframes fade-in-hover-card + from + opacity: 0 + to + opacity: 1 + .op-hover-card + animation: fade-in-hover-card .25s ease-in-out position: absolute background-color: var(--body-background) z-index: 5000 diff --git a/frontend/src/app/shared/components/principal/principal-renderer.service.ts b/frontend/src/app/shared/components/principal/principal-renderer.service.ts index eff236ea5d2f..8c7565d15edf 100644 --- a/frontend/src/app/shared/components/principal/principal-renderer.service.ts +++ b/frontend/src/app/shared/components/principal/principal-renderer.service.ts @@ -6,12 +6,19 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; import { IPrincipal } from 'core-app/core/state/principals/principal.model'; import { PrincipalLike } from './principal-types'; import { hrefFromPrincipal, PrincipalType, typeFromHref } from './principal-helper'; +import { PortalOutletTarget } from 'core-app/shared/components/modal/portal-outlet-target.enum'; export type AvatarSize = 'default'|'medium'|'mini'; +export interface HoverCardOptions { + url?:string; + modalTarget?:PortalOutletTarget; +} + export interface AvatarOptions { hide:boolean; size:AvatarSize; + hoverCard?:HoverCardOptions; } export interface NameOptions { @@ -99,6 +106,11 @@ export class PrincipalRendererService { container.classList.add('op-principal'); const type = typeFromHref(hrefFromPrincipal(principal)) as PrincipalType; + // Only actual users provide a hover card with additional info + if (type !== 'user') { + avatar.hoverCard = undefined; + } + if (!avatar.hide) { const el = this.renderAvatar(principal, avatar, type); container.appendChild(el); @@ -129,6 +141,8 @@ export class PrincipalRendererService { fallback.title = principal.name; fallback.textContent = userInitials; + this.setHoverCardAttributes(fallback, options, principal); + if (type === 'placeholder_user' && colorMode !== colorModes.lightHighContrast) { fallback.style.color = colorCode; fallback.style.borderColor = colorCode; @@ -144,7 +158,11 @@ export class PrincipalRendererService { return fallback; } - private renderUserAvatar(principal:PrincipalLike|IPrincipal, fallback:HTMLElement, options:AvatarOptions):void { + private renderUserAvatar( + principal:PrincipalLike|IPrincipal, + fallback:HTMLElement, + options:AvatarOptions, + ):void { const url = this.userAvatarUrl(principal); if (!url) { @@ -155,6 +173,9 @@ export class PrincipalRendererService { image.classList.add('op-principal--avatar'); image.classList.add('op-avatar'); image.classList.add(`op-avatar_${options.size}`); + + this.setHoverCardAttributes(image, options, principal); + image.src = url; image.title = principal.name; image.alt = principal.name; @@ -170,6 +191,11 @@ export class PrincipalRendererService { return id ? this.apiV3Service.users.id(id).avatar.toString() : null; } + private userHoverCardUrl(principal:PrincipalLike|IPrincipal):string|null { + const id = principal.id || idFromLink(hrefFromPrincipal(principal)); + return id ? this.pathHelper.userHoverCardPath(id) : null; + } + private renderName( principal:PrincipalLike|IPrincipal, type:PrincipalType, @@ -226,4 +252,24 @@ export class PrincipalRendererService { const last = name[lastSpace + 1]?.toUpperCase(); return [first, last].join(''); } + + private setHoverCardAttributes(element:HTMLElement, options:AvatarOptions, principal:PrincipalLike|IPrincipal):void { + const hoverCard = options.hoverCard; + + if (!hoverCard?.url) { + // In some cases, there is no URL given although a hover card is expected. For example when the principle + // is rendered from an angular template. We try to infer the URL here. + const url = this.userHoverCardUrl(principal); + if (hoverCard && url) { + hoverCard.url = url; + } else { + return; + } + } + + element.classList.add('op-hover-card--preview-trigger'); + + element.setAttribute('data-hover-card-url', hoverCard.url); + element.setAttribute('data-hover-card-target', String(hoverCard.modalTarget || PortalOutletTarget.Default)); + } } diff --git a/frontend/src/app/shared/components/principal/principal.component.ts b/frontend/src/app/shared/components/principal/principal.component.ts index 8a8e4ac5c4df..3d521120a7f1 100644 --- a/frontend/src/app/shared/components/principal/principal.component.ts +++ b/frontend/src/app/shared/components/principal/principal.component.ts @@ -40,14 +40,12 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; -import { - AvatarSize, - PrincipalRendererService, -} from './principal-renderer.service'; +import { AvatarOptions, AvatarSize, PrincipalRendererService } from './principal-renderer.service'; import { PrincipalLike } from './principal-types'; import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs'; import { PrincipalType } from 'core-app/shared/components/principal/principal-helper'; import { PrincipalsResourceService } from 'core-app/core/state/principals/principals.service'; +import { PortalOutletTarget } from 'core-app/shared/components/modal/portal-outlet-target.enum'; export const principalSelector = 'op-principal'; @@ -76,6 +74,12 @@ export class OpPrincipalComponent implements OnInit { @Input() size:AvatarSize = 'default'; + @Input() avatarClasses? = ''; + + @Input() hoverCard= true; + @Input() hoverCardUrl= ''; + @Input() hoverCardModalTarget:'default'|'custom' = 'default'; + @Input() title = ''; public constructor( @@ -93,6 +97,19 @@ export class OpPrincipalComponent implements OnInit { ngOnInit() { if (this.principal.name) { + const avatarOptions:AvatarOptions = { + hide: this.hideAvatar, + size: this.size, + }; + + if (this.hoverCard) { + avatarOptions.hoverCard = { + url: this.hoverCardUrl, + modalTarget: this.hoverCardModalTarget === 'custom' + ? PortalOutletTarget.Custom : PortalOutletTarget.Default, + }; + } + this.principalRenderer.render( this.elementRef.nativeElement as HTMLElement, this.principal, @@ -101,10 +118,7 @@ export class OpPrincipalComponent implements OnInit { link: this.link, classes: this.nameClasses, }, - { - hide: this.hideAvatar, - size: this.size, - }, + avatarOptions, this.title === '' ? null : this.title, ); } diff --git a/frontend/src/app/shared/components/time_entries/create/create.modal.ts b/frontend/src/app/shared/components/time_entries/create/create.modal.ts index 6025be38a499..50d90c4493e6 100644 --- a/frontend/src/app/shared/components/time_entries/create/create.modal.ts +++ b/frontend/src/app/shared/components/time_entries/create/create.modal.ts @@ -1,14 +1,17 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, InjectionToken } from '@angular/core'; import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; import { TimeEntryResource } from 'core-app/features/hal/resources/time-entry-resource'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { TimeEntryBaseModal } from '../shared/modal/base.modal'; +export const SHOW_USER_HOVER_CARD = new InjectionToken('SHOW_USER_HOVER_CARD'); + @Component({ templateUrl: '../shared/modal/base.modal.html', changeDetection: ChangeDetectionStrategy.OnPush, providers: [ HalResourceEditingService, + { provide: SHOW_USER_HOVER_CARD, useValue: false }, ], }) export class TimeEntryCreateModalComponent extends TimeEntryBaseModal { diff --git a/frontend/src/global_styles/common/header/app-header.sass b/frontend/src/global_styles/common/header/app-header.sass index 28c081a03a16..87f17c2e29ad 100644 --- a/frontend/src/global_styles/common/header/app-header.sass +++ b/frontend/src/global_styles/common/header/app-header.sass @@ -83,4 +83,4 @@ padding-right: 1rem &.op-app-header_development - background-image: url("data:image/svg+xml,development") + background-image: url("data:image/svg+xml,development") diff --git a/lib/redmine/menu_manager/top_menu_helper.rb b/lib/redmine/menu_manager/top_menu_helper.rb index 1881d9b12bf4..059b72f2b604 100644 --- a/lib/redmine/menu_manager/top_menu_helper.rb +++ b/lib/redmine/menu_manager/top_menu_helper.rb @@ -138,7 +138,7 @@ def render_direct_login end def render_user_drop_down(items) - avatar = avatar(User.current, class: "op-top-menu-user-avatar") + avatar = avatar(User.current, class: "op-top-menu-user-avatar", hover_card: { active: false }) render_menu_dropdown_with_items( label: avatar.presence || "", label_options: { diff --git a/lookbook/docs/patterns/25-hover-cards.md.erb b/lookbook/docs/patterns/25-hover-cards.md.erb index 2bb9f2bc0a9d..18474ed9eaee 100644 --- a/lookbook/docs/patterns/25-hover-cards.md.erb +++ b/lookbook/docs/patterns/25-hover-cards.md.erb @@ -1,8 +1,10 @@ -The HoverCard is a pattern related to the `Primer::Beta::Popover` and is used to show additional contexual information on certain kinds of resources like work packages and users. The hover card is opened by hovering over a certain trigger. When hovering outside of the card or its trigger, the popover is closed again. +The HoverCard is a pattern related to the `Primer::Beta::Popover` and is used to show additional contextual information on certain kinds of resources like work packages and users. The hover card is opened by hovering over a certain trigger. When hovering outside of the card or its trigger, the popover is closed again. ## Overview -![Exemplary hover card](<%= image_path('lookbook/hover_card.png') %>) +![Exemplary work package hover card](<%= image_path('lookbook/hover_card.png') %>) + +![Exemplary user hover card](<%= image_path('lookbook/user_hover_card.png') %>) ## Anatomy @@ -27,7 +29,7 @@ The HoverCard always consists of two basic parts: ## Used in - WorkPackage preview when linking via `#ID` -- Soon: User preview when hovering the avatar +- User preview when hovering the avatar ## Technical notes @@ -61,13 +63,27 @@ Additionally, the trigger element needs to pass the URL for the `turboFrame` as class="op-hover-card--preview-trigger"> #14 + + + + + ``` +Note that the user example is simplified. For actual use in the application, it is recommended to use the `AvatarComponent`, which offers an option for hover cards. + **Actually rendered card content**: ```html <%= render WorkPackages::HoverCardComponent.new(id: 14) %> - %> + + + + <%= render Users::HoverCardComponent.new(id: 14) %> + ``` diff --git a/lookbook/previews/open_project/users/avatar_component_preview.rb b/lookbook/previews/open_project/users/avatar_component_preview.rb index afc90b99f790..b2e500a4281d 100644 --- a/lookbook/previews/open_project/users/avatar_component_preview.rb +++ b/lookbook/previews/open_project/users/avatar_component_preview.rb @@ -3,13 +3,17 @@ module OpenProject::Users # @logical_path OpenProject/Users class AvatarComponentPreview < Lookbook::Preview - # Renders a user avatar using the OpenProject opce-principal web component + # Renders a user avatar using the OpenProject opce-principal web component. Note that the hover card options + # have no effect in this lookbook. # @param size select { choices: [default, medium, mini] } # @param link toggle # @param show_name toggle - def default(size: :default, link: true, show_name: true) + # @param hover_card toggle + # @param hover_card_target select { choices: [default, custom] } + def default(size: :default, link: true, show_name: true, hover_card: true, hover_card_target: :default) user = FactoryBot.build_stubbed(:user) - render(Users::AvatarComponent.new(user:, size:, link:, show_name:)) + render(Users::AvatarComponent.new(user:, size:, link:, show_name:, + hover_card: { active: hover_card, target: hover_card_target })) end def sizes diff --git a/modules/avatars/app/helpers/avatar_helper.rb b/modules/avatars/app/helpers/avatar_helper.rb index 97672ef0dc4c..97f9eea7486b 100644 --- a/modules/avatars/app/helpers/avatar_helper.rb +++ b/modules/avatars/app/helpers/avatar_helper.rb @@ -45,8 +45,8 @@ module AvatarHelper # Returns the avatar image tag for the given +user+ if avatars are enabled # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') - def avatar(principal, size: "default", hide_name: true, name_classes: "", **) - build_principal_avatar_tag(principal, size:, hide_name:, name_classes:, **) + def avatar(principal, size: "default", hide_name: true, hover_card: { active: true, target: :default }, name_classes: "", **) + build_principal_avatar_tag(principal, size:, hide_name:, hover_card:, name_classes:, **) rescue StandardError => e Rails.logger.error "Failed to create avatar for #{principal}: #{e}" "".html_safe @@ -99,22 +99,27 @@ def build_principal_avatar_tag(user, **) id: user.id } + inputs = { + principal:, + link: tag_options[:link], + size: tag_options[:size], + hideName: tag_options[:hide_name], + nameClasses: tag_options[:name_classes], + title: tag_options.fetch(:title, "") + } + + inputs = hover_card_options(user, inputs, tag_options) + angular_component_tag "opce-principal", class: tag_options[:class], - inputs: { - principal:, - link: tag_options[:link], - size: tag_options[:size], - hideName: tag_options[:hide_name], - nameClasses: tag_options[:name_classes], - title: tag_options.fetch(:title, "") - } + inputs: end def merge_default_avatar_options(user, options) default_options = { size: "default", - hide_name: true + hide_name: true, + hover_card: {} } default_options[:title] = h(user.name) if user.respond_to?(:name) @@ -136,4 +141,22 @@ def extract_email_address(object) object.mail end end + + private + + def hover_card_options(user, inputs = {}, tag_options = {}) + # The hover card will be triggered by hovering over the avatar (if enabled) + hover_card = tag_options[:hover_card] + if hover_card.fetch(:active, true) + inputs[:hoverCard] = true + inputs[:hoverCardModalTarget] = hover_card.fetch(:target, :default) + + inputs[:hoverCardUrl] = hover_card_user_path(user.id) + else + # We must explicitly set this to false since the angular renderer defines their own default to `true` + inputs[:hoverCard] = false + end + + inputs + end end diff --git a/modules/avatars/spec/helpers/avatar_helper_spec.rb b/modules/avatars/spec/helpers/avatar_helper_spec.rb index bee8c25b06e6..37b0149856d6 100644 --- a/modules/avatars/spec/helpers/avatar_helper_spec.rb +++ b/modules/avatars/spec/helpers/avatar_helper_spec.rb @@ -24,22 +24,27 @@ allow(user).to receive(:local_avatar_attachment).and_return avatar_stub end - def expected_user_avatar_tag(user) + def expected_user_avatar_tag(user, hover_card: true, modal_target: "default", hover_card_url: "/users/#{user.id}/hover_card") principal = { href: "/api/v3/users/#{user.id}", name: user.name, id: user.id } - angular_component_tag "opce-principal", - inputs: { - principal:, - hideName: true, - nameClasses: "", - link: nil, - title: user.name, - size: "default" - } + inputs = { + principal:, + hideName: true, + nameClasses: "", + link: nil, + title: user.name, + size: "default", + hoverCard: hover_card + } + + inputs[:hoverCardModalTarget] = modal_target if hover_card + inputs[:hoverCardUrl] = hover_card_url if hover_card + + angular_component_tag "opce-principal", inputs: end def local_expected_url(user) @@ -210,4 +215,16 @@ def gravatar_expected_url(digest, options = {}) expect(helper.avatar(user)).to be_html_eql(expected_user_avatar_tag(user)) end end + + context "when using hover cards" do + it "can be disabled" do + avatar = helper.avatar(user, hover_card: { active: false }) + expect(avatar).to be_html_eql(expected_user_avatar_tag(user, hover_card: false)) + end + + it "provides a custom modal target" do + avatar = helper.avatar(user, hover_card: { target: :custom }) + expect(avatar).to be_html_eql(expected_user_avatar_tag(user, modal_target: :custom)) + end + end end diff --git a/modules/boards/spec/features/board_navigation_spec.rb b/modules/boards/spec/features/board_navigation_spec.rb index f6cbf815b460..dc39dc6d3aa3 100644 --- a/modules/boards/spec/features/board_navigation_spec.rb +++ b/modules/boards/spec/features/board_navigation_spec.rb @@ -183,6 +183,9 @@ # Add a new WP on the board board_page = board_index.open_board board_view + + wait_for_network_idle + board_page.expect_query "List 1", editable: true board_page.add_card "List 1", "Task 1" board_page.expect_toast message: I18n.t(:notice_successful_create) @@ -203,7 +206,10 @@ destroy_modal.expect_listed(wp) destroy_modal.confirm_deletion - board_page.expect_empty + wait_for_network_idle + + board_page.expect_query "List 1", editable: true + board_page.expect_not_any_card board_page.expect_path end end diff --git a/modules/boards/spec/features/support/board_page.rb b/modules/boards/spec/features/support/board_page.rb index 969cc6f0ce3f..84043b583880 100644 --- a/modules/boards/spec/features/support/board_page.rb +++ b/modules/boards/spec/features/support/board_page.rb @@ -240,6 +240,10 @@ def expect_empty expect(page).to have_no_css(".boards-list--item", wait: 10) end + def expect_not_any_card + expect(page).to have_no_css('[data-test-selector="op-wp-single-card"]') + end + def remove_list(name) click_list_dropdown name, "Delete list" diff --git a/modules/costs/config/locales/crowdin/es.yml b/modules/costs/config/locales/crowdin/es.yml index 7327e6bfdfd2..97fb0763c1a3 100644 --- a/modules/costs/config/locales/crowdin/es.yml +++ b/modules/costs/config/locales/crowdin/es.yml @@ -133,10 +133,10 @@ es: permission_view_own_hourly_rate: "Ver tarifa por hora propia" permission_view_own_time_entries: "Ver tiempo propio invertido" project_module_costs: "Tiempo y costos" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Permitir a los usuarios realizar un seguimiento de la hora de inicio y fin en los registros de tiempo" + setting_costs_currency: "Moneda" + setting_costs_currency_format: "Formato de moneda" + setting_enforce_tracking_start_and_end_times: "Forzar a los usuarios a fijar la hora de inicio y fin en los registros horarios" text_assign_time_and_cost_entries_to_project: "Asignar horas reportadas y los costos al proyecto" text_destroy_cost_entries_question: "%{cost_entries} fueron reportados en los paquetes de trabajo que van a eliminar. ¿Qué quieres hacer?" text_destroy_time_and_cost_entries: "Eliminar horas reportadas y costos" diff --git a/modules/costs/config/locales/crowdin/fr.yml b/modules/costs/config/locales/crowdin/fr.yml index b57fa3d525d1..f805ac698f6e 100644 --- a/modules/costs/config/locales/crowdin/fr.yml +++ b/modules/costs/config/locales/crowdin/fr.yml @@ -133,10 +133,10 @@ fr: permission_view_own_hourly_rate: "Voir son propre tarif horaire" permission_view_own_time_entries: "Voir son propre temps passé" project_module_costs: "Temps et coûts" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Permettre aux utilisateurs de suivre l'heure de début et de fin sur les relevés de temps" + setting_costs_currency: "Devise" + setting_costs_currency_format: "Format de devise" + setting_enforce_tracking_start_and_end_times: "Obliger les utilisateurs à définir des heures de début et de fin pour les relevés de temps" text_assign_time_and_cost_entries_to_project: "Assigner les heures et coûts consignés au projet" text_destroy_cost_entries_question: "%{cost_entries} ont été consignés sur les lots de travaux que vous êtes sur le point de supprimer. Que voulez-vous faire ?" text_destroy_time_and_cost_entries: "Supprimer les heures et coûts consignés" diff --git a/modules/costs/config/locales/crowdin/it.yml b/modules/costs/config/locales/crowdin/it.yml index 035b5d58d35d..5b26448b6a1e 100644 --- a/modules/costs/config/locales/crowdin/it.yml +++ b/modules/costs/config/locales/crowdin/it.yml @@ -133,10 +133,10 @@ it: permission_view_own_hourly_rate: "Visualizza propria tariffa oraria" permission_view_own_time_entries: "Visualizzare il proprio tempo trascorso" project_module_costs: "Tempi e costi" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Consentire agli utenti di tenere traccia dell'ora di inizio e di fine nelle registrazioni temporali" + setting_costs_currency: "Valuta" + setting_costs_currency_format: "Formato della valuta" + setting_enforce_tracking_start_and_end_times: "Forza gli utenti a impostare l'ora di inizio e di fine nelle registrazioni temporali" text_assign_time_and_cost_entries_to_project: "Assegna orari e costi segnalati al progetto" text_destroy_cost_entries_question: "%{cost_entries} sono stati segnalati per la macro-attività (work package) che si sta per eliminare. Che cosa vuoi fare?" text_destroy_time_and_cost_entries: "Elimina orari e costi riportati" diff --git a/modules/costs/config/locales/crowdin/pl.yml b/modules/costs/config/locales/crowdin/pl.yml index 8a5db5ae8891..bbbc6aba7ac0 100644 --- a/modules/costs/config/locales/crowdin/pl.yml +++ b/modules/costs/config/locales/crowdin/pl.yml @@ -135,10 +135,10 @@ pl: permission_view_own_hourly_rate: "Zobacz własne stawki godzinowe" permission_view_own_time_entries: "Zobacz swój poświęcony czas" project_module_costs: "Czas i koszty" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Zezwalaj użytkownikom na śledzenie czasu rozpoczęcia i zakończenia w rekordach czasu" + setting_costs_currency: "Waluta" + setting_costs_currency_format: "Format waluty" + setting_enforce_tracking_start_and_end_times: "Wymuszaj na użytkownikach ustawienie czasu rozpoczęcia i zakończenia w rekordach czasu" text_assign_time_and_cost_entries_to_project: "Przypisz zgłoszone godziny i koszty do projektu" text_destroy_cost_entries_question: "%{cost_entries} zostały zgłoszone do Zestawu zadań, który chcesz usunąć. Co chcesz zrobić?" text_destroy_time_and_cost_entries: "Usuń zgłoszone godziny i koszty" diff --git a/modules/costs/config/locales/crowdin/pt-BR.yml b/modules/costs/config/locales/crowdin/pt-BR.yml index 13ea16d2c347..9667fbec777a 100644 --- a/modules/costs/config/locales/crowdin/pt-BR.yml +++ b/modules/costs/config/locales/crowdin/pt-BR.yml @@ -133,10 +133,10 @@ pt-BR: permission_view_own_hourly_rate: "Ver sua própria taxa horária" permission_view_own_time_entries: "Ver o próprio tempo gasto" project_module_costs: "Tempo e custos" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Permita que os usuários acompanhem os horários de início e término nos registros de tempo" + setting_costs_currency: "Moeda" + setting_costs_currency_format: "Formato de moeda" + setting_enforce_tracking_start_and_end_times: "Torne obrigatório que os usuários especifiquem os horários de início e término nos registros de tempo" text_assign_time_and_cost_entries_to_project: "Atribuir horas relatadas e custos ao projeto" text_destroy_cost_entries_question: "%{cost_entries} foram informados sobre os pacotes de trabalho que você está prestes a excluir. O que você quer fazer?" text_destroy_time_and_cost_entries: "Excluir horas e custos informados" diff --git a/modules/costs/config/locales/crowdin/pt-PT.yml b/modules/costs/config/locales/crowdin/pt-PT.yml index 8d5e5de51112..452739465ddc 100644 --- a/modules/costs/config/locales/crowdin/pt-PT.yml +++ b/modules/costs/config/locales/crowdin/pt-PT.yml @@ -133,10 +133,10 @@ pt-PT: permission_view_own_hourly_rate: "Ver taxa horária própria" permission_view_own_time_entries: "Ver tempo próprio gasto" project_module_costs: "Tempo e custos" - setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" - setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" + setting_allow_tracking_start_and_end_times: "Permita que os utilizadores acompanhem a hora de início e de fim nos registos de tempo" + setting_costs_currency: "Moeda" + setting_costs_currency_format: "Formato de moeda" + setting_enforce_tracking_start_and_end_times: "Torne obrigatório que os utilizadores definam a hora de início e de fim nos registos de tempo" text_assign_time_and_cost_entries_to_project: "Atribuir horas e custos reportados ao projeto" text_destroy_cost_entries_question: "%{cost_entries} foram reportados nos pacotes de trabalho que está prestes a apagar. Tem a certeza que pretende apagar?" text_destroy_time_and_cost_entries: "Apagar horas e custos reportados" diff --git a/modules/costs/config/locales/crowdin/vi.yml b/modules/costs/config/locales/crowdin/vi.yml index 5224ff0d4e56..5fb121235027 100644 --- a/modules/costs/config/locales/crowdin/vi.yml +++ b/modules/costs/config/locales/crowdin/vi.yml @@ -108,7 +108,7 @@ vi: label_rate: "Tỷ lệ" label_rate_plural: "Các tỷ lệ" label_status_finished: "Hoàn thành" - label_show: "Show" + label_show: "Hiện" label_units: "Đơn vị chi phí" label_user: "Người dùng" label_until: "đến" @@ -133,8 +133,8 @@ vi: permission_view_own_time_entries: "Xem thời gian đã tiêu của chính mình" project_module_costs: "Thời gian và chi phí" setting_allow_tracking_start_and_end_times: "Allow users to track start and end time on time records" - setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency: "Đơn vị tiền tệ" + setting_costs_currency_format: "Định dạng tiền tệ" setting_enforce_tracking_start_and_end_times: "Force users to set start and end time on time records" text_assign_time_and_cost_entries_to_project: "Gán giờ và chi phí đã báo cáo cho dự án" text_destroy_cost_entries_question: "%{cost_entries} đã được báo cáo trên các gói công việc bạn sắp xóa. Bạn muốn làm gì?" diff --git a/modules/meeting/config/locales/crowdin/es.yml b/modules/meeting/config/locales/crowdin/es.yml index ce25bf7e8061..c0a14b844ed7 100644 --- a/modules/meeting/config/locales/crowdin/es.yml +++ b/modules/meeting/config/locales/crowdin/es.yml @@ -88,7 +88,7 @@ es: label_meeting: "Reunión" label_meeting_plural: "Reuniones" label_meeting_new: "Nueva reunión" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nueva reunión dinámica" label_meeting_create: "Crear reunión" label_meeting_copy: "Copiar reunión" label_meeting_edit: "Editar reunión" diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index 9eb430858890..7124b8c570eb 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -88,7 +88,7 @@ fr: label_meeting: "Réunion" label_meeting_plural: "Réunions" label_meeting_new: "Nouvelle réunion" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nouvelle réunion dynamique" label_meeting_create: "Créer une réunion" label_meeting_copy: "Copier la réunion" label_meeting_edit: "Modifier la réunion" diff --git a/modules/meeting/config/locales/crowdin/it.yml b/modules/meeting/config/locales/crowdin/it.yml index 6f6bba17c1dd..52395b3a5ed0 100644 --- a/modules/meeting/config/locales/crowdin/it.yml +++ b/modules/meeting/config/locales/crowdin/it.yml @@ -88,7 +88,7 @@ it: label_meeting: "Riunione" label_meeting_plural: "Riunioni" label_meeting_new: "Nuova riunione" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nuova riunione dinamica" label_meeting_create: "Crea riunione" label_meeting_copy: "Copia riunione" label_meeting_edit: "Modifica riunione" diff --git a/modules/meeting/config/locales/crowdin/pl.yml b/modules/meeting/config/locales/crowdin/pl.yml index b527739d60ef..35d987c87acc 100644 --- a/modules/meeting/config/locales/crowdin/pl.yml +++ b/modules/meeting/config/locales/crowdin/pl.yml @@ -90,7 +90,7 @@ pl: label_meeting: "Spotkanie" label_meeting_plural: "Spotkania" label_meeting_new: "Nowe spotkanie" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nowe spotkanie dynamiczne" label_meeting_create: "Utwórz spotkanie" label_meeting_copy: "Skopiuj spotkanie" label_meeting_edit: "Edycja spotkania" diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index 91ef69304bf2..2ff08e8659b9 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -88,7 +88,7 @@ pt-BR: label_meeting: "Reunião" label_meeting_plural: "Reuniões" label_meeting_new: "Nova Reunião" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nova reunião dinâmica" label_meeting_create: "Criar reunião" label_meeting_copy: "Copiar reunião" label_meeting_edit: "Editar Reunião" diff --git a/modules/meeting/config/locales/crowdin/pt-PT.yml b/modules/meeting/config/locales/crowdin/pt-PT.yml index 54a72d47d27e..e9d75b137f7f 100644 --- a/modules/meeting/config/locales/crowdin/pt-PT.yml +++ b/modules/meeting/config/locales/crowdin/pt-PT.yml @@ -88,7 +88,7 @@ pt-PT: label_meeting: "Reunião" label_meeting_plural: "Reuniões" label_meeting_new: "Nova reunião" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Nova reunião dinâmica" label_meeting_create: "Criar reunião" label_meeting_copy: "Copiar reunião" label_meeting_edit: "Editar a reunião" diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index 9e1726dc48d5..5c5a4ce14414 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -90,7 +90,7 @@ uk: label_meeting: "Зустріч" label_meeting_plural: "Зустрічі" label_meeting_new: "Нова зустріч" - label_meeting_new_dynamic: "New dynamic meeting" + label_meeting_new_dynamic: "Нова динамічна нарада" label_meeting_create: "Створити нараду" label_meeting_copy: "Копіювати нараду" label_meeting_edit: "Редагувати збори" diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index f4f22acb1943..83b64e576237 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -88,15 +88,15 @@ vi: label_meeting_plural: "Những cuộc họp" label_meeting_new: "Cuộc họp mới" label_meeting_new_dynamic: "New dynamic meeting" - label_meeting_create: "Create meeting" + label_meeting_create: "Tạo cuộc họp" label_meeting_copy: "Copy meeting" label_meeting_edit: "Chỉnh sửa cuộc họp" label_meeting_agenda: "Chương trình nghị sự" label_meeting_minutes: "Biên bản cuộc họp" label_meeting_close: "Đóng" label_meeting_open: "Mở" - label_meeting_index_delete: "Delete" - label_meeting_open_this_meeting: "Open this meeting" + label_meeting_index_delete: "Xoá" + label_meeting_open_this_meeting: "Mở cuộc họp này" label_meeting_agenda_close: "Đóng chương trình nghị sự để bắt đầu biên bản cuộc họp" label_meeting_date_time: "Ngày/Giờ" label_meeting_date_and_time: "Date and time" diff --git a/modules/openid_connect/config/locales/crowdin/es.yml b/modules/openid_connect/config/locales/crowdin/es.yml index e95110ca1cda..52ab06aaf19f 100644 --- a/modules/openid_connect/config/locales/crowdin/es.yml +++ b/modules/openid_connect/config/locales/crowdin/es.yml @@ -59,10 +59,10 @@ es: metadata_url: Tengo una URL del terminal de descubrimiento client_id: Este es el ID de cliente que le ha proporcionado su proveedor de OpenID Connect client_secret: Este es el secreto de cliente que le ha proporcionado su proveedor de OpenID Connect - limit_self_registration: If enabled, users can only register using this provider if configuration on the provider's end allows it. - display_name: The name of the provider. This will be displayed as the login button and in the list of providers. + limit_self_registration: Si está activada, los usuarios solo podrán registrarse utilizando este proveedor si la configuración del proveedor lo permite. + display_name: El nombre del proveedor. Aparecerá como botón de inicio de sesión y en la lista de proveedores. tenant: 'Sustituya el inquilino predeterminado por el suyo si procede. Consulte esto.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Si desea solicitar ámbitos personalizados, puede añadir aquí uno o varios valores de ámbito separados por espacios. Para más información, consulte la [documentación de OpenID Connect](docs_url). post_logout_redirect_uri: La URL a la que debe redirigir el proveedor de OpenID Connect tras una solicitud de cierre de sesión. claims: > Puede solicitar reclamaciones adicionales para los terminales userinfo e id token. Consulte [nuestra documentación sobre OpenID connect](docs_url) para obtener más información. @@ -83,7 +83,7 @@ es: metadata_url: Tengo una URL del terminal de descubrimiento endpoint_url: URL del terminal providers: - label_providers: "Providers" + label_providers: "Proveedores" seeded_from_env: "Este proveedor fue sembrado desde la configuración del entorno. No puede editarse." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/fr.yml b/modules/openid_connect/config/locales/crowdin/fr.yml index 4bea344da781..293744ae3b84 100644 --- a/modules/openid_connect/config/locales/crowdin/fr.yml +++ b/modules/openid_connect/config/locales/crowdin/fr.yml @@ -59,10 +59,10 @@ fr: metadata_url: J'ai une URL de point de terminaison de découverte client_id: Il s'agit de l'ID de client qui vous a été attribué par votre fournisseur OpenID Connect client_secret: Il s'agit de la clé secrète du client qui vous a été communiquée par votre fournisseur OpenID Connect - limit_self_registration: If enabled, users can only register using this provider if configuration on the provider's end allows it. - display_name: The name of the provider. This will be displayed as the login button and in the list of providers. + limit_self_registration: Si cette option est activée, les utilisateurs peuvent s'inscrire par le biais de ce fournisseur uniquement si la configuration du côté du fournisseur le permet. + display_name: Le nom du fournisseur. Il sera affiché comme bouton de connexion et dans la liste des fournisseurs. tenant: 'Veuillez remplacer le locataire par défaut par le vôtre, le cas échéant. Vous pouvez en savoir plus ici.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Si vous voulez demander des portées personnalisées, vous pouvez ajouter une ou plusieurs valeurs de portée séparées par des espaces. Consultez voir la [documentation OpenID Connect](docs_url) pour obtenir plus d'informations. post_logout_redirect_uri: L'URL vers laquelle le fournisseur OpenID Connect doit rediriger le client après une demande de déconnexion. claims: > Vous pouvez demander des réclamations supplémentaires pour les points de terminaison userinfo et id token. Veuillez consulter [notre documentation OpenID Connect](docs_url) pour obtenir plus d'informations. @@ -83,7 +83,7 @@ fr: metadata_url: J'ai une URL de point de terminaison de découverte endpoint_url: URL du point de terminaison providers: - label_providers: "Providers" + label_providers: "Fournisseurs" seeded_from_env: "Ce fournisseur a été ajouté à partir de la configuration de l'environnement. Il ne peut pas être modifié." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/it.yml b/modules/openid_connect/config/locales/crowdin/it.yml index 023f79935c83..2fe433ece02c 100644 --- a/modules/openid_connect/config/locales/crowdin/it.yml +++ b/modules/openid_connect/config/locales/crowdin/it.yml @@ -59,10 +59,10 @@ it: metadata_url: Ho un URL endpoint di ricerca client_id: Questo è l'ID client che ti viene assegnato dal tuo fornitore OpenID Connect client_secret: Questa è la chiave segreta del client che ti viene assegnata dal tuo fornitore OpenID Connect - limit_self_registration: If enabled, users can only register using this provider if configuration on the provider's end allows it. - display_name: The name of the provider. This will be displayed as the login button and in the list of providers. + limit_self_registration: Se l'opzione è abilitata, gli utenti possono registrarsi utilizzando questo fornitore solo se la configurazione da parte del fornitore lo consente. + display_name: Il nome del fornitore. Questo verrà visualizzato come pulsante di accesso e nell'elenco dei fornitori. tenant: 'Sostituisci il tenant predefinito con il proprio se applicabile. Consulta questa pagina.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Se vuoi richiedere ambiti personalizzati, puoi aggiungere uno o più valori di ambito separati da spazi qui. Per maggiori informazioni, consulta la [documentazione di OpenID Connect](docs_url). post_logout_redirect_uri: L'URL a cui il fornitore OpenID Connect deve reindirizzare dopo una richiesta di logout. claims: > Puoi effettuare richieste aggiuntive per gli endpoint userinfo e id token. Per maggiori informazioni, consulta [la nostra documentazione OpenID connect](docs_url). @@ -83,7 +83,7 @@ it: metadata_url: Ho un URL endpoint di ricerca endpoint_url: URL dell'endpoint providers: - label_providers: "Providers" + label_providers: "Fornitori" seeded_from_env: "Questo fornitore è stato salvato dalla configurazione dell'ambiente. Non può essere modificato." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/pl.yml b/modules/openid_connect/config/locales/crowdin/pl.yml index ce779c7dd5c0..e856b86be59b 100644 --- a/modules/openid_connect/config/locales/crowdin/pl.yml +++ b/modules/openid_connect/config/locales/crowdin/pl.yml @@ -59,10 +59,10 @@ pl: metadata_url: Mam adres URL punktu końcowego odkrywania client_id: Jest to identyfikator klienta nadany Ci przez dostawcę OpenID Connect client_secret: Jest to klucz tajny klienta podany Ci przez dostawcę OpenID Connect - limit_self_registration: If enabled, users can only register using this provider if configuration on the provider's end allows it. - display_name: The name of the provider. This will be displayed as the login button and in the list of providers. + limit_self_registration: Jeśli opcja jest włączona, użytkownicy mogą rejestrować się przy użyciu tego dostawcy tylko wtedy, gdy pozwala na to konfiguracja po stronie dostawcy. + display_name: Nazwa dostawcy. Zostanie wyświetlona jako przycisk logowania i element listy dostawców. tenant: 'W razie potrzeby zastąp dzierżawcę domyślnego własnym. Zobacz to.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Jeśli chcesz zażądać niestandardowych zakresów, możesz dodać tutaj jedną lub wiele wartości zakresu rozdzielonych spacjami. Więcej informacji można znaleźć w [dokumentacji OpenID Connect](docs_url). post_logout_redirect_uri: Adres URL, do którego ma przekierowywać dostawca OpenID Connect po żądaniu wylogowania. claims: > Możesz zażądać dodatkowych oświadczeń dla punktów końcowych userinfo i id token. Więcej informacji znajdziesz w [naszej dokumentacji OpenID connect](docs_url). @@ -83,7 +83,7 @@ pl: metadata_url: Mam adres URL punktu końcowego odkrywania endpoint_url: Adres URL punktu końcowego providers: - label_providers: "Providers" + label_providers: "Dostawcy" seeded_from_env: "Ten dostawca został zainicjowany z konfiguracji środowiska. Nie można go edytować." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/pt-BR.yml b/modules/openid_connect/config/locales/crowdin/pt-BR.yml index 49ceae3c4537..7812220bcc50 100644 --- a/modules/openid_connect/config/locales/crowdin/pt-BR.yml +++ b/modules/openid_connect/config/locales/crowdin/pt-BR.yml @@ -62,7 +62,7 @@ pt-BR: limit_self_registration: Se ativado, os usuários só poderão se registrar usando este provedor se a configuração no lado do provedor permitir. display_name: Nome do provedor. Ele será exibido como o botão de login e na lista de provedores. tenant: 'Substitua o locatário padrão pelo seu, se necessário. Consulte aqui.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Se você quiser solicitar escopos personalizados, pode adicionar um ou vários valores de escopo separados por espaços aqui. Para mais informações, consulte a [documentação do OpenID Connect](docs_url). post_logout_redirect_uri: O URL para a qual o provedor OpenID Connect deve redirecionar após a solicitação de logout. claims: > Você pode solicitar reivindicações adicionais para os endpoints userinfo e id token. Consulte [nossa documentação sobre OpenID Connect](docs_url) para obter mais informações. @@ -83,7 +83,7 @@ pt-BR: metadata_url: Tenho um URL de endpoint de descoberta endpoint_url: URL de Endpoint providers: - label_providers: "Providers" + label_providers: "Provedores" seeded_from_env: "Este provedor foi configurado a partir das configurações do ambiente e não pode ser editado." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/pt-PT.yml b/modules/openid_connect/config/locales/crowdin/pt-PT.yml index df4c222d033e..e3b6128502c9 100644 --- a/modules/openid_connect/config/locales/crowdin/pt-PT.yml +++ b/modules/openid_connect/config/locales/crowdin/pt-PT.yml @@ -62,7 +62,7 @@ pt-PT: limit_self_registration: Se estiver ativado, os utilizadores só podem registar-se utilizando este fornecedor se a configuração do fornecedor o permitir. display_name: O nome do fornecedor. Este nome será apresentado no botão de início de sessão e na lista de fornecedores. tenant: 'Substitua o inquilino predefinido pelo seu próprio inquilino, se aplicável. Consulte este artigo.' - scope: If you want to request custom scopes, you can add one or multiple scope values separated by spaces here. For more information, see the [OpenID Connect documentation](docs_url). + scope: Se pretender solicitar âmbitos personalizados, pode adicionar um ou vários valores de âmbito separados por espaços aqui. Para obter mais informações, consulte a [documentação do OpenID Connect](docs_url). post_logout_redirect_uri: O URL para o qual o fornecedor do OpenID Connect deve redirecionar após um pedido de encerramento de sessão. claims: > Pode solicitar reclamações adicionais para os endpoints userinfo e id token. Consulte [a nossa documentação sobre o OpenID connect](docs_url) para obter mais informações. @@ -83,7 +83,7 @@ pt-PT: metadata_url: Tenho um URL de endpoint de descoberta endpoint_url: URL do Endpoint providers: - label_providers: "Providers" + label_providers: "Fornecedores" seeded_from_env: "Este fornecedor foi semeado a partir da configuração do ambiente. Não pode ser editado." google: name: Google diff --git a/modules/openid_connect/config/locales/crowdin/uk.yml b/modules/openid_connect/config/locales/crowdin/uk.yml index c9237d8ba7a7..2e23f69e6d10 100644 --- a/modules/openid_connect/config/locales/crowdin/uk.yml +++ b/modules/openid_connect/config/locales/crowdin/uk.yml @@ -83,7 +83,7 @@ uk: metadata_url: У мене є URL-адреса кінцевої точки виявлення endpoint_url: URL кінцевої точки providers: - label_providers: "Providers" + label_providers: "Постачальники" seeded_from_env: "Цього постачальника послуг додано з конфігурації середовища. Його не можна змінити." google: name: Google diff --git a/modules/reporting/app/models/cost_query/filter/user_id.rb b/modules/reporting/app/models/cost_query/filter/user_id.rb index 79d9b089b11e..ade496258899 100644 --- a/modules/reporting/app/models/cost_query/filter/user_id.rb +++ b/modules/reporting/app/models/cost_query/filter/user_id.rb @@ -52,7 +52,7 @@ def replace_me_value(value) def self.available_values(*) # All users which are members in projects the user can see. # Excludes the anonymous user - users = User.visible + users = User.in_visible_project .human .ordered_by_name .select(User::USER_FORMATS_STRUCTURE[Setting.user_format].map(&:to_s) << :id) diff --git a/modules/storages/app/common/storages/peripherals/nextcloud_connection_validator.rb b/modules/storages/app/common/storages/peripherals/nextcloud_connection_validator.rb index 371d24b18ccf..399f871d21a3 100644 --- a/modules/storages/app/common/storages/peripherals/nextcloud_connection_validator.rb +++ b/modules/storages/app/common/storages/peripherals/nextcloud_connection_validator.rb @@ -31,6 +31,7 @@ module Storages module Peripherals class NextcloudConnectionValidator + include TaggedLogging include Dry::Monads[:maybe] using ServiceResultRefinements @@ -40,13 +41,15 @@ def initialize(storage:) end def validate - maybe_is_not_configured - .or { has_base_configuration_error? } - .or { has_ampf_configuration_error? } - .value_or(ConnectionValidation.new(type: :healthy, - error_code: :none, - timestamp: Time.current, - description: nil)) + with_tagged_logger do + maybe_is_not_configured + .or { has_base_configuration_error? } + .or { has_ampf_configuration_error? } + .value_or(ConnectionValidation.new(type: :healthy, + error_code: :none, + timestamp: Time.current, + description: nil)) + end end private @@ -194,6 +197,11 @@ def with_unexpected_content unexpected_files = files.result.files.reject { |file| expected_folder_ids.include?(file.id) } return None() if unexpected_files.empty? + file_representation = unexpected_files.map do |file| + "Name: #{file.name}, ID: #{file.id}, Location: #{file.location}" + end + warn "Unexpected files/folder found in group folder:\n\t#{file_representation.join("\n\t")}" + Some( ConnectionValidation.new( type: :warning, @@ -209,13 +217,11 @@ def with_unexpected_content def capabilities_request_failed_with_unknown_error return None() if capabilities.success? - Rails.logger.error( - "Connection validation failed with unknown error:\n\t" \ - "storage: ##{@storage.id} #{@storage.name}\n\t" \ - "request: Nextcloud capabilities\n\t" \ - "status: #{capabilities.result}\n\t" \ - "response: #{capabilities.error_payload}" - ) + error "Connection validation failed with unknown error:\n\t" \ + "storage: ##{@storage.id} #{@storage.name}\n\t" \ + "request: Nextcloud capabilities\n\t" \ + "status: #{capabilities.result}\n\t" \ + "response: #{capabilities.error_payload}" Some(ConnectionValidation.new(type: :error, error_code: :err_unknown, @@ -226,13 +232,11 @@ def capabilities_request_failed_with_unknown_error def files_request_failed_with_unknown_error return None() if files.success? - Rails.logger.error( - "Connection validation failed with unknown error:\n\t" \ - "storage: ##{@storage.id} #{@storage.name}\n\t" \ - "request: Group folder content\n\t" \ - "status: #{files.result}\n\t" \ - "response: #{files.error_payload}" - ) + error "Connection validation failed with unknown error:\n\t" \ + "storage: ##{@storage.id} #{@storage.name}\n\t" \ + "request: Group folder content\n\t" \ + "status: #{files.result}\n\t" \ + "response: #{files.error_payload}" Some(ConnectionValidation.new(type: :error, error_code: :err_unknown, diff --git a/modules/storages/app/common/storages/peripherals/one_drive_connection_validator.rb b/modules/storages/app/common/storages/peripherals/one_drive_connection_validator.rb index 911f8280f51c..5c62084e37d7 100644 --- a/modules/storages/app/common/storages/peripherals/one_drive_connection_validator.rb +++ b/modules/storages/app/common/storages/peripherals/one_drive_connection_validator.rb @@ -31,6 +31,7 @@ module Storages module Peripherals class OneDriveConnectionValidator + include TaggedLogging include Dry::Monads[:maybe] using ServiceResultRefinements @@ -40,17 +41,19 @@ def initialize(storage:) end def validate - maybe_is_not_configured - .or { tenant_id_wrong } - .or { client_id_wrong } - .or { client_secret_wrong } - .or { drive_id_wrong } - .or { request_failed_with_unknown_error } - .or { drive_with_unexpected_content } - .value_or(ConnectionValidation.new(type: :healthy, - error_code: :none, - timestamp: Time.current, - description: nil)) + with_tagged_logger do + maybe_is_not_configured + .or { tenant_id_wrong } + .or { client_id_wrong } + .or { client_secret_wrong } + .or { drive_id_wrong } + .or { request_failed_with_unknown_error } + .or { drive_with_unexpected_content } + .value_or(ConnectionValidation.new(type: :healthy, + error_code: :none, + timestamp: Time.current, + description: nil)) + end end private @@ -146,6 +149,11 @@ def drive_with_unexpected_content unexpected_files = query.result.files.reject { |file| expected_folder_ids.include?(file.id) } return None() if unexpected_files.empty? + file_representation = unexpected_files.map do |file| + "Name: #{file.name}, ID: #{file.id}, Location: #{file.location}" + end + warn "Unexpected files/folder found in group folder:\n\t#{file_representation.join("\n\t")}" + Some(ConnectionValidation.new(type: :warning, error_code: :wrn_unexpected_content, timestamp: Time.current, @@ -157,10 +165,10 @@ def drive_with_unexpected_content def request_failed_with_unknown_error return None() if query.success? - Rails.logger.error("Connection validation failed with unknown error:\n\t" \ - "storage: ##{@storage.id} #{@storage.name}\n\t" \ - "status: #{query.result}\n\t" \ - "response: #{query.error_payload}") + error "Connection validation failed with unknown error:\n\t" \ + "storage: ##{@storage.id} #{@storage.name}\n\t" \ + "status: #{query.result}\n\t" \ + "response: #{query.error_payload}" Some(ConnectionValidation.new(type: :error, error_code: :err_unknown, diff --git a/modules/storages/app/common/storages/tagged_logging.rb b/modules/storages/app/common/storages/tagged_logging.rb index 28d18c4884fd..2a5e353435e9 100644 --- a/modules/storages/app/common/storages/tagged_logging.rb +++ b/modules/storages/app/common/storages/tagged_logging.rb @@ -30,7 +30,7 @@ module Storages module TaggedLogging - delegate :info, :error, to: :logger + delegate :info, :warn, :error, to: :logger # @param tag [Class, String, Array] the tag or list of tags to annotate the logs with # @yield [Logger] diff --git a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec index 08ec023b31dc..c0cb74181ac2 100644 --- a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec +++ b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec @@ -14,6 +14,6 @@ Gem::Specification.new do |s| s.add_dependency "rotp", "~> 6.1" s.add_dependency "webauthn", "~> 3.0" - s.add_dependency "aws-sdk-sns", "~> 1.90.0" + s.add_dependency "aws-sdk-sns", "~> 1.92.0" s.metadata["rubygems_mfa_required"] = "true" end diff --git a/spec/components/users/hover_card_component_spec.rb b/spec/components/users/hover_card_component_spec.rb new file mode 100644 index 000000000000..789823d3d86f --- /dev/null +++ b/spec/components/users/hover_card_component_spec.rb @@ -0,0 +1,140 @@ +# 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. +#++ + +require "rails_helper" + +RSpec.describe Users::HoverCardComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:another_user) { create(:user, member_with_permissions: { project => [:manage_members] }) } + let(:current_user) { another_user } + + let(:groups) { [] } + + subject { described_class.new(id: user.id) } + + before do + groups + login_as(current_user) + render_inline(subject) + page.extend TestSelectorFinders + end + + it "renders successfully" do + page.find_test_selector("user-hover-card-name", text: user.name) + end + + context "when the user does not exist" do + let(:user) { instance_double(User, id: 9000) } + + it "renders a generic error message" do + expect(page).to have_text(I18n.t("http.response.unexpected")) + end + end + + context "when displaying email addresses" do + it "hides the email address of a user" do + expect(page).not_to have_test_selector("user-hover-card-email") + end + + context "with the rights to view email addresses" do + # Admin is allowed to see emails + let(:current_user) { build(:admin) } + + it "shows the email address of a user" do + page.find_test_selector("user-hover-card-email", text: user.mail) + end + end + end + + context "when showing the group summary" do + it "shows a no results text for users without group memberships" do + g = page.find_test_selector("user-hover-card-groups") + expect(g).to have_text(I18n.t("users.groups.no_results_title_text")) + end + + context "with the user being member of some groups" do + let(:groups) do + Array.new(2) { create(:group, members: user) } + end + + it "lists the group names for a user" do + g = page.find_test_selector("user-hover-card-groups") + + expect(g).to have_text("Member of #{groups.first.lastname}, #{groups.last.lastname}.") + end + + context "with no rights to manage members" do + # No manage_members permission: + let(:another_user) { create(:user) } + + it "does not show groups" do + g = page.find_test_selector("user-hover-card-groups") + + expect(g).to have_text(I18n.t("users.groups.no_results_title_text")) + end + end + end + + context "with the user being member of many groups" do + let(:groups) do + Array.new(8) { create(:group, members: user) } + end + + it "lists some group names with truncation" do + g = page.find_test_selector("user-hover-card-groups") + + expect(g).to have_text("Member of #{groups.slice(0, 4).map(&:lastname).join(', ')} and 4 more.") + end + end + end + + context "when clicking on the Open Profile button" do + it "leads to the users profile" do + b = page.find_test_selector("user-hover-card-profile-btn") + + expect(b).to have_text(I18n.t("users.open_profile")) + expect(b["href"]).to eq(user_path(user)) + end + + context "with the right to manage users" do + let(:current_user) { build(:admin) } + + it "leads to editing the users profile" do + b = page.find_test_selector("user-hover-card-profile-btn") + + expect(b).to have_text(I18n.t("users.open_profile")) + expect(b["href"]).to eq(edit_user_path(user)) + end + end + end +end diff --git a/spec/features/activities/work_package/activities_spec.rb b/spec/features/activities/work_package/activities_spec.rb index 51adef83a422..0b00261c4d60 100644 --- a/spec/features/activities/work_package/activities_spec.rb +++ b/spec/features/activities/work_package/activities_spec.rb @@ -82,6 +82,14 @@ member_with_roles: { project => user_role_with_editing_permission }) end + let(:comment_work_package_role) { create(:comment_work_package_role) } + let(:user_with_commenting_permission_via_a_work_package_share) do + create(:user, + firstname: "A", + lastname: "Commenter", + member_with_roles: { work_package => comment_work_package_role }) + end + let(:work_package) { create(:work_package, project:, author: admin) } let(:first_comment) do create(:work_package_journal, user: admin, notes: "First comment by admin", journable: work_package, @@ -205,6 +213,22 @@ end end end + + context "when a user has been shared a work package with at least comment rights" do + current_user { user_with_commenting_permission_via_a_work_package_share } + + before do + wp_page.visit! + wp_page.wait_for_activity_tab + end + + it "allows commenting on the work package" do + activity_tab.expect_input_field + + activity_tab.add_comment(text: "First comment by user with commenting permission via a work package share") + activity_tab.expect_journal_notes(text: "First comment by user with commenting permission via a work package share") + end + end end context "when a workpackage is created and visited by the same user" do diff --git a/spec/features/members/membership_spec.rb b/spec/features/members/membership_spec.rb index 8e33149e5273..850125f8e771 100644 --- a/spec/features/members/membership_spec.rb +++ b/spec/features/members/membership_spec.rb @@ -138,6 +138,22 @@ expect(members_page).not_to have_group group.name end + it "shows more information when hovering over an avatar" do + members_page.in_user_row(peter) do |row| + # Hover over the avatar of peter to open the hover card + row.find(".op-principal--avatar").hover + end + + members_page.in_user_hover_card(peter) do + find_test_selector("user-hover-card-name", text: peter.name) + find_test_selector("user-hover-card-email", text: peter.mail) + find_test_selector("user-hover-card-groups", text: "Member of #{peter.groups.first.name}") + + button = find_test_selector("user-hover-card-profile-btn", text: "Open profile") + expect(button["href"]).to eq(edit_user_url(peter)) + end + end + context "as a member" do current_user { peter } diff --git a/spec/features/work_packages/details/relations/primerized_relations_spec.rb b/spec/features/work_packages/details/relations/primerized_relations_spec.rb index 56bc66d3699e..f80169512089 100644 --- a/spec/features/work_packages/details/relations/primerized_relations_spec.rb +++ b/spec/features/work_packages/details/relations/primerized_relations_spec.rb @@ -41,7 +41,8 @@ set_factory_default(:project_with_types, project) end - shared_let(:work_package) { create(:work_package, subject: "main") } + shared_let(:parent_work_package) { create(:work_package, subject: "parent") } + shared_let(:work_package) { create(:work_package, subject: "main", parent: parent_work_package) } shared_let(:type1) { create(:type) } shared_let(:type2) { create(:type) } @@ -142,10 +143,13 @@ def label_for_relation_type(relation_type) relation_row = relations_tab.expect_relation(relation_follows) - relations_tab.add_description_to_relation(relation_follows, "Discovered relations have descriptions!") + relations_tab.edit_relation_description(relation_follows, "Discovered relations have descriptions!") - # Reflects new description + relations_tab.edit_lag_of_relation(relation_follows, 5) + + # Reflects new description and lag expect(relation_row).to have_text("Discovered relations have descriptions!") + expect(relation_row).to have_text("5 days") # Unchanged tabs.expect_counter("relations", 4) @@ -171,6 +175,17 @@ def label_for_relation_type(relation_type) end end + it "does not show the lag field for all relation types" do + scroll_to_element relations_panel + + relations_tab.open_relation_dialog(relation_relates) + + within "##{WorkPackageRelationsTab::WorkPackageRelationDialogComponent::DIALOG_ID}" do + expect(page).to have_field("Work package", readonly: true) + expect(page).to have_no_field("Lag") + end + end + context "with the shown WorkPackage being the 'to' relation part" do let(:another_wp) { create(:work_package, type: type2, subject: "related to main") } @@ -215,13 +230,7 @@ def label_for_relation_type(relation_type) # wp_predecessor is already related to work_package as relation_follows # in a predecessor relation, so it should not be autocompleteable anymore # under the "Predecessor (before)" type - scroll_to_element relations_panel - - relations_panel.find("[data-test-selector='new-relation-action-menu']").click - - within page.find_by_id("new-relation-action-menu-list") do # Primer appends "list" to the menu id automatically - click_link_or_button "Predecessor (before)" - end + relations_tab.select_relation_type "Predecessor (before)" wait_for_reload @@ -251,6 +260,43 @@ def label_for_relation_type(relation_type) # Bumped by one tabs.expect_counter("relations", 5) end + + it "doesn't autocomplete parent, children, and WP itself" do + relations_tab.select_relation_type "Child" + + wait_for_reload + + within "##{WorkPackageRelationsTab::AddWorkPackageChildFormComponent::DIALOG_ID}" do + autocomplete_field = page.find("[data-test-selector='work-package-child-form-id']") + + # It doesn't autocomplete children + search_autocomplete(autocomplete_field, + query: child_wp.subject, + results_selector: "body") + + expect_no_ng_option(autocomplete_field, + child_wp.subject, + results_selector: "body") + + # It doesn't autocomplete parent + search_autocomplete(autocomplete_field, + query: parent_work_package.subject, + results_selector: "body") + + expect_no_ng_option(autocomplete_field, + parent_work_package.subject, + results_selector: "body") + + # It doesn't autocomplete work package itself + search_autocomplete(autocomplete_field, + query: work_package.id, + results_selector: "body") + + expect_no_ng_option(autocomplete_field, + work_package.id, + results_selector: "body") + end + end end describe "with limited permissions" do diff --git a/spec/models/custom_field/work_package_custom_field_spec.rb b/spec/models/custom_field/work_package_custom_field_spec.rb new file mode 100644 index 000000000000..6f1c6afd9b13 --- /dev/null +++ b/spec/models/custom_field/work_package_custom_field_spec.rb @@ -0,0 +1,74 @@ +# 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. +#++ + +require "spec_helper" + +RSpec.describe WorkPackageCustomField, :model do + let!(:bool_cf) { create(:boolean_wp_custom_field) } + let!(:text_cf) { create(:text_wp_custom_field) } + let!(:hierarchy_cf) { create(:hierarchy_wp_custom_field) } + + describe "scopes" do + context "if user has permission to select custom fields" do + let(:user) { create(:user) } + let(:role) { create(:project_role, permissions: [:select_custom_fields]) } + let!(:project) { create(:project, members: { user => role }) } + + it "returns all custom fields" do + expect(described_class.visible_by_user(user)).to contain_exactly(bool_cf, text_cf, hierarchy_cf) + end + end + + context "if user has no access to a project with the enabled custom field" do + let(:user) { create(:user) } + let(:role) { create(:project_role, permissions: []) } + let(:feature) { create(:type_feature) } + let(:task) { create(:type_task) } + let(:project) { create(:project, members: { user => role }, types: [feature, task]) } + let(:project_without_user) { create(:project, types: [feature, task]) } + + before do + project.work_package_custom_fields << text_cf + feature.custom_fields << text_cf + project_without_user.work_package_custom_fields << bool_cf + task.custom_fields << bool_cf + # hierarchy_cf is not enabled in any project + end + + it "returns only custom fields that are enabled in projects the user has access to" do + expect(described_class.visible_by_user(user)).to contain_exactly(text_cf) + end + end + + it "returns custom fields that are usable as custom action" do + expect(described_class.usable_as_custom_action).to contain_exactly(bool_cf, text_cf) + end + end +end diff --git a/spec/requests/api/v3/authentication_spec.rb b/spec/requests/api/v3/authentication_spec.rb index 658232433892..c95a1817b8b6 100644 --- a/spec/requests/api/v3/authentication_spec.rb +++ b/spec/requests/api/v3/authentication_spec.rb @@ -424,7 +424,7 @@ def set_basic_auth_header(user, password) headers: { "Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "JSON::JWK::Set::Fetcher 2.8.2" + "User-Agent" => "JSON::JWK::Set::Fetcher 2.9.0" } ) .to_return(status: 200, body: '{"keys":[{"kid":"CANAG6lJUPKqKDoWxxXL5wAHf2U18BAzm_LJm7RPTGk","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"nqJexS6n-SxKSDUxXp_dsNwDW6cZ4Rtgqq9ut_lp1CNSph5wTnLG3aQQsTEvx5o3-SZ-pHjJ0gtEpg7clAz-w-YQyZoAXkFtQqmZJxsmdS4K0yILxO3WUNdJQlutjmq-Ri50Senn5IV7yEYWLo8St1qzUqWZhp0HKudyty24triC9UJTK03W3_Tr5c1X8vKL8duAjvLB7p_sYUOrnLq5pD5lqwxVSAiN8qS5zVNZMrhGV5aN1vN_vue_tw8c2SVOCLLTrUh3441rYaeo-UwQZF7ZTm30xflqAIfe8qMoB20wtWYAXR0D5iqkkdEH4XanCYVm5vdUFIPPvXZhRDWoNQ","e":"AQAB","x5c":["MIICmzCCAYMCBgGQupeGPzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwNzE2MDgwODMwWhcNMzQwNzE2MDgxMDEwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCeol7FLqf5LEpINTFen92w3ANbpxnhG2Cqr263+WnUI1KmHnBOcsbdpBCxMS/Hmjf5Jn6keMnSC0SmDtyUDP7D5hDJmgBeQW1CqZknGyZ1LgrTIgvE7dZQ10lCW62Oar5GLnRJ6efkhXvIRhYujxK3WrNSpZmGnQcq53K3Lbi2uIL1QlMrTdbf9OvlzVfy8ovx24CO8sHun+xhQ6ucurmkPmWrDFVICI3ypLnNU1kyuEZXlo3W83++57+3DxzZJU4IstOtSHfjjWthp6j5TBBkXtlObfTF+WoAh97yoygHbTC1ZgBdHQPmKqSR0QfhdqcJhWbm91QUg8+9dmFENag1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAB/AGvP0gviPoJszj/oQgBsMpPGRHLpnTmrXnTaa7Xk2sgExAb4zUAwxGjtR347t697cpiKQYBkR2ndswnt93Sx/Ot+yn5BdYcNvZuEh5jb5bkH2V4h6/LrYljTymby+XPBEf+XLhBOjoI3SKtNJk4pEqVNwLuKKbObqJcE3G3VBVSdzRUcIrjZr7yAQeLnhczS3hJ0Ct6Y7S5Q6DK+/PU1+AvlW+7GfzpRMqVfLcqhNpRwdCVGlJYKaUJfIe1vav10D94xA0U1sKex3iA1S+1HlS2BCWx/0rXwgcquMpUZlOAKiT0K6SIFxBFFnM9eQbF97Dz7Bzw+jyqStGUcH9YA="],"x5t":"TuBfrOL00KXDrOWTv3jw7Uxx3hA","x5t#S256":"7su5lOXF5qcMuvp44ynsoyk3B0l9Sr_bOVlg768shpY"},{"kid":"97AmyvoS8BFFRfm585GPgA16G1H2V22EdxxuAYUuoKk","kty":"RSA","alg":"RS256","use":"sig","n":"jMB2r7BG4QJzLnA2_fgG1mxlh2RX_MSx0lc2lrPIVFGYBuAu8irwRLSexX5aQdD_AtnxLD4g9jiG6VEDwmWopEe0fr-QMl0IiES5tJuQMrjhajOkzr8xTYu6zl-knL0tu99iRbmKNYzEcv0TAgY_95n4gD5tPhYvY4gXuHrFKqYkJQPsSgoThlH7hAtfzsDt6yp3P2lQUESGg3pzc_J_NKnQkkggcNB06Hlz4DmcHxhWXK51P1V9cE7qh4PrhsJ-SOH5grcN9PtOZi6f2VlWdFdyisT-YehNklfVqBtdCLm7Ocghhl0HSgLuV-9dHCdwBLUpABsdsd0L3LRCUgRfjQ","e":"AQAB","x5c":["MIICmzCCAYMCBgGQupeFFTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwNzE2MDgwODMwWhcNMzQwNzE2MDgxMDEwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCMwHavsEbhAnMucDb9+AbWbGWHZFf8xLHSVzaWs8hUUZgG4C7yKvBEtJ7FflpB0P8C2fEsPiD2OIbpUQPCZaikR7R+v5AyXQiIRLm0m5AyuOFqM6TOvzFNi7rOX6ScvS2732JFuYo1jMRy/RMCBj/3mfiAPm0+Fi9jiBe4esUqpiQlA+xKChOGUfuEC1/OwO3rKnc/aVBQRIaDenNz8n80qdCSSCBw0HToeXPgOZwfGFZcrnU/VX1wTuqHg+uGwn5I4fmCtw30+05mLp/ZWVZ0V3KKxP5h6E2SV9WoG10Iubs5yCGGXQdKAu5X710cJ3AEtSkAGx2x3QvctEJSBF+NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIoBCsOO0bXiVspoXkqdOts4+3sULbbp5aEwQscmLX017Zvv5jxdkZxUYk8L08lNB+WlC1ES4VlmtE06D0cWYErGpArJzVBKgYSA3CkA9veBEugHviMqfwg3suNc8S+GtaRBvpbVZtXydjjqA8GZ4eKhPoJLHHCX6X2Ad33Cdt0/ftucjTqAKVzzzgWZejy+ZKP6ybAqYJ+EZoPUXlyWT3uwcpGEJ3nzOYYGTfxOSmAwnH2v5Z/JWr9ex5o/+QBuBhFcg0z8NcHa3Z0E6ZC9GGxV7XztBqYicO+nONHTLCctoJmyXvLM4j8qIG2UQgPIiwIL0Jkz6xQAYyXvsb+LhM8="],"x5t":"BFrni6MoX-CJwtMT4vzij1HBSTI","x5t#S256":"-Ge3y4JRezxhGTDfbkNoz7prkokzYtbKQ9ardPtfcz4"}]}', headers: {}) @@ -495,7 +495,7 @@ def set_basic_auth_header(user, password) headers: { "Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "JSON::JWK::Set::Fetcher 2.8.2" + "User-Agent" => "JSON::JWK::Set::Fetcher 2.9.0" } ) .to_return(status: 200, body: '{"keys":[{"kid":"CANAG6lJUPKqKDoWxxXL5wAHf2U18BAzm_LJm7RPTGk","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"nqJexS6n-SxKSDUxXp_dsNwDW6cZ4Rtgqq9ut_lp1CNSph5wTnLG3aQQsTEvx5o3-SZ-pHjJ0gtEpg7clAz-w-YQyZoAXkFtQqmZJxsmdS4K0yILxO3WUNdJQlutjmq-Ri50Senn5IV7yEYWLo8St1qzUqWZhp0HKudyty24triC9UJTK03W3_Tr5c1X8vKL8duAjvLB7p_sYUOrnLq5pD5lqwxVSAiN8qS5zVNZMrhGV5aN1vN_vue_tw8c2SVOCLLTrUh3441rYaeo-UwQZF7ZTm30xflqAIfe8qMoB20wtWYAXR0D5iqkkdEH4XanCYVm5vdUFIPPvXZhRDWoNQ","e":"AQAB","x5c":["MIICmzCCAYMCBgGQupeGPzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwNzE2MDgwODMwWhcNMzQwNzE2MDgxMDEwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCeol7FLqf5LEpINTFen92w3ANbpxnhG2Cqr263+WnUI1KmHnBOcsbdpBCxMS/Hmjf5Jn6keMnSC0SmDtyUDP7D5hDJmgBeQW1CqZknGyZ1LgrTIgvE7dZQ10lCW62Oar5GLnRJ6efkhXvIRhYujxK3WrNSpZmGnQcq53K3Lbi2uIL1QlMrTdbf9OvlzVfy8ovx24CO8sHun+xhQ6ucurmkPmWrDFVICI3ypLnNU1kyuEZXlo3W83++57+3DxzZJU4IstOtSHfjjWthp6j5TBBkXtlObfTF+WoAh97yoygHbTC1ZgBdHQPmKqSR0QfhdqcJhWbm91QUg8+9dmFENag1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAB/AGvP0gviPoJszj/oQgBsMpPGRHLpnTmrXnTaa7Xk2sgExAb4zUAwxGjtR347t697cpiKQYBkR2ndswnt93Sx/Ot+yn5BdYcNvZuEh5jb5bkH2V4h6/LrYljTymby+XPBEf+XLhBOjoI3SKtNJk4pEqVNwLuKKbObqJcE3G3VBVSdzRUcIrjZr7yAQeLnhczS3hJ0Ct6Y7S5Q6DK+/PU1+AvlW+7GfzpRMqVfLcqhNpRwdCVGlJYKaUJfIe1vav10D94xA0U1sKex3iA1S+1HlS2BCWx/0rXwgcquMpUZlOAKiT0K6SIFxBFFnM9eQbF97Dz7Bzw+jyqStGUcH9YA="],"x5t":"TuBfrOL00KXDrOWTv3jw7Uxx3hA","x5t#S256":"7su5lOXF5qcMuvp44ynsoyk3B0l9Sr_bOVlg768shpY"},{"kid":"9755555S8BFFRfm585GPgA16G1H2V22EdxxuAYUuoKk","kty":"RSA","alg":"RS256","use":"sig","n":"jMB2r7BG4QJzLnA2_fgG1mxlh2RX_MSx0lc2lrPIVFGYBuAu8irwRLSexX5aQdD_AtnxLD4g9jiG6VEDwmWopEe0fr-QMl0IiES5tJuQMrjhajOkzr8xTYu6zl-knL0tu99iRbmKNYzEcv0TAgY_95n4gD5tPhYvY4gXuHrFKqYkJQPsSgoThlH7hAtfzsDt6yp3P2lQUESGg3pzc_J_NKnQkkggcNB06Hlz4DmcHxhWXK51P1V9cE7qh4PrhsJ-SOH5grcN9PtOZi6f2VlWdFdyisT-YehNklfVqBtdCLm7Ocghhl0HSgLuV-9dHCdwBLUpABsdsd0L3LRCUgRfjQ","e":"AQAB","x5c":["MIICmzCCAYMCBgGQupeFFTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwNzE2MDgwODMwWhcNMzQwNzE2MDgxMDEwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCMwHavsEbhAnMucDb9+AbWbGWHZFf8xLHSVzaWs8hUUZgG4C7yKvBEtJ7FflpB0P8C2fEsPiD2OIbpUQPCZaikR7R+v5AyXQiIRLm0m5AyuOFqM6TOvzFNi7rOX6ScvS2732JFuYo1jMRy/RMCBj/3mfiAPm0+Fi9jiBe4esUqpiQlA+xKChOGUfuEC1/OwO3rKnc/aVBQRIaDenNz8n80qdCSSCBw0HToeXPgOZwfGFZcrnU/VX1wTuqHg+uGwn5I4fmCtw30+05mLp/ZWVZ0V3KKxP5h6E2SV9WoG10Iubs5yCGGXQdKAu5X710cJ3AEtSkAGx2x3QvctEJSBF+NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIoBCsOO0bXiVspoXkqdOts4+3sULbbp5aEwQscmLX017Zvv5jxdkZxUYk8L08lNB+WlC1ES4VlmtE06D0cWYErGpArJzVBKgYSA3CkA9veBEugHviMqfwg3suNc8S+GtaRBvpbVZtXydjjqA8GZ4eKhPoJLHHCX6X2Ad33Cdt0/ftucjTqAKVzzzgWZejy+ZKP6ybAqYJ+EZoPUXlyWT3uwcpGEJ3nzOYYGTfxOSmAwnH2v5Z/JWr9ex5o/+QBuBhFcg0z8NcHa3Z0E6ZC9GGxV7XztBqYicO+nONHTLCctoJmyXvLM4j8qIG2UQgPIiwIL0Jkz6xQAYyXvsb+LhM8="],"x5t":"BFrni6MoX-CJwtMT4vzij1HBSTI","x5t#S256":"-Ge3y4JRezxhGTDfbkNoz7prkokzYtbKQ9ardPtfcz4"}]}', headers: {}) diff --git a/spec/support/components/work_packages/relations.rb b/spec/support/components/work_packages/relations.rb index 2c992d574349..69ca5493c2aa 100644 --- a/spec/support/components/work_packages/relations.rb +++ b/spec/support/components/work_packages/relations.rb @@ -80,6 +80,14 @@ def expect_no_row(relatable) expect(page).not_to have_test_selector("op-relation-row-#{actual_relatable.id}") end + def select_relation_type(relation_type) + page.find_test_selector("new-relation-action-menu").click + + within page.find_by_id("new-relation-action-menu-list") do + click_link_or_button relation_type + end + end + def remove_relation(relatable) actual_relatable = find_relatable(relatable) relatable_row = find_row(actual_relatable) @@ -131,12 +139,10 @@ def add_relation(type:, relatable:, description: nil) # Open create form SeleniumHubWaiter.wait - page.find_test_selector("new-relation-action-menu").click label_text_for_relation_type = I18n.t("#{i18n_namespace}.label_#{type}_singular") - within page.find_by_id("new-relation-action-menu-list") do # Primer appends "list" to the menu id automatically - click_link_or_button label_text_for_relation_type.capitalize - end + + select_relation_type label_text_for_relation_type.capitalize wait_for_reload if using_cuprite? @@ -168,7 +174,7 @@ def add_relation(type:, relatable:, description: nil) find_row(target_wp) end - def add_description_to_relation(relatable, description) + def edit_relation_description(relatable, description) open_relation_dialog(relatable) within "##{WorkPackageRelationsTab::WorkPackageRelationDialogComponent::DIALOG_ID}" do @@ -183,14 +189,14 @@ def add_description_to_relation(relatable, description) end end - def edit_relation_description(relatable, description) + def edit_lag_of_relation(relatable, lag) open_relation_dialog(relatable) within "##{WorkPackageRelationsTab::WorkPackageRelationDialogComponent::DIALOG_ID}" do expect(page).to have_field("Work package", readonly: true) - expect(page).to have_field("Description") + expect(page).to have_field("Lag") - fill_in "Description", with: description + fill_in "Lag", with: lag click_link_or_button "Save" @@ -272,11 +278,7 @@ def add_existing_child(work_package) SeleniumHubWaiter.wait retry_block do - page.find_test_selector("new-relation-action-menu").click - - within page.find_by_id("new-relation-action-menu-list") do # Primer appends "list" to the menu id automatically - click_link_or_button "Child" - end + select_relation_type "Child" end within "##{WorkPackageRelationsTab::AddWorkPackageChildFormComponent::DIALOG_ID}" do diff --git a/spec/support/pages/members.rb b/spec/support/pages/members.rb index 0dbe8cafe1ef..d3367ee23fe6 100644 --- a/spec/support/pages/members.rb +++ b/spec/support/pages/members.rb @@ -82,6 +82,10 @@ def in_user_row(user, &) page.within(".principal-#{user.id}", &) end + def in_user_hover_card(user, &) + page.within_test_selector("user-hover-card-#{user.id}", &) + end + ## # Adds the given user to this project. #